Typelevel alchemist

Cats ecosystem in practice

Jakub Kozłowski - Scala Developer, Ocado Technology Scala Wave | September 7, 2018 | Gdańsk, Poland

Agenda

  1. Typeclasses recap
  2. Higher-kinded types
  3. Referential transparency
  4. Tagless Final
  5. Cats intro
  6. Cats-effect
  7. fs2, http4s, doobie
  8. patterns for applications

About me

  • Live in Wrocław, Poland
  • Doing Scala for 3 years
  • Worked @ Scalac all that time
  • now @ Ocado Technology
  • I love ramen
  • Running and lifting when not injured

Links

Typeclasses recap

  • What's a typeclass?
  • Examples
  • Coherence
  • Alternatives

What's a typeclass

Example: collapsing a list

Collapsing a list


Hard to extend - need a new implementation AND name for every type in list
Let's try to make that abstract

Original idea from the Cats documentation

Monoid typeclass

Usage of new implementation

Typeclasses should have laws

Coherence

  • Each pairing of (type, typeclass implementation) must be unique
  • Not enforced by Scala


Takeaway: Not all traits with implicit instances are typeclasses

Alternatives

Runtime reflection

  • e.g. JSON serializers in Jackson or json4s
  • Hard to extend (registering instances in global, mutable scope)
  • No compile-time checks, no runtime safety

Pattern matching

  • Non-extensible
  • No compile-time checks, no runtime safety
  • Still hard with generics (requires ClassTag, which is... a typeclass)

Comparison

Extensible Generics Runtime safe
Reflection
Pattern matching
Subtyping
Typeclasses

Polymorphism via subtyping

Subtyping

  • How to collapse an empty list?
  • Pollutes the implementing classes
  • All or nothing
  • Can't implement for types we can't control

So, typeclasses...

Standard library examples (pre-2.13)

Higher-kinded types

Types

*-kinded

String, Int, Boolean, List[Int]

higher-kinded (* -> *)

Option[_], List[_], Future[_]

higher-kinded types are "incomplete" - they have a hole

also called "type constructors"

Higher-kinded types

Type-level functions

f      =  x  => f(x)      //function lambda
Option = [A] => Option[A] //type lambda

Higher-kinded types need to be applied with a type to become a *-kinded type

Functions vs higher-kinded types

Abstracting on types

Type lambdas

With kind-projector

Why we need type lambdas

Partial unification

The compiler can infer this (with a flag, by default in 2.13):
Unfortunately, IDEA can't (yet) :(

Tagless Final

Fancy name for abstracting on types*
* - not a precise definition, but it'll suffice

Why tagless?

  • We don't couple ourselves to a specific effect like IO
  • We can easily swap effects for testing or if we decide to switch
  • Easier to work with monad transformers (explained later)
  • Sometimes allows for not leaking implementation details
  • More power to users of your library

Referential transparency

If an expression t can be substituted by its value a, then it's referentially transparent
//if a function f(x) is referentially transparent
//and x is referentially transparent

val a = f(x)

(a, a) <-> (f(x), f(x))

Breaking RT - Future

Future breaks most of the time

Future can break even more

It hurts

It also breaks in the other direction

Throwing breaks too

What's a (pure) function?

Meet IO

Measure time with IO

Predictable nukes with IO

Why go referentially transparent?

  • Equational reasoning
  • Programs as values
  • Testing
  • Local reasoning
  • Explicit effects, predictable code

Equational reasoning

Refactor fearlessly

Programs as values

Pass your programs to others so that they add meaning

Testing

Local reasoning

  • Split programs more easily
  • Compose smaller functions into larger ones
  • Reason independently

Local reasoning

A wild implicit side effect appears!

Find the side effect

(line 13, limit)

Explicit effects

Checklist

  • Is it calling the DB?
  • Is it calling HTTP?
  • Is it logging?
  • Is it caching?
  • Is it changing state?
  • Is it reading from a cache?
  • Can it throw?
  • Can it have a different result if you call it tomorrow?


If you have ≥1 checks, it's effectful - slap an IO on it

Predictable results

A pure function can have a limited number of implementations
def f[A](a: A): A

has one

How many implementations?

def f9: IO[Unit]

How many implementations?

def f9: IO[Unit]

How many implementations?

How many implementations?

Cats

Cats

Core typelevel project

  • Typeclasses
  • Data types
  • Syntax

Typeclasses in cats

  • Eq
  • Order
  • Monoid
  • Functor
  • Applicative
  • Monad
  • ApplicativeError
  • MonadError
  • Traverse...

Eq

Multiversal equality

Note: typeclass definitions in the slides are simplified

Syntax for Cats typeclasses

Tip: use consistent import style to avoid conflicts

Eq exercise

Write a function that checks if all elements of a List[T] are equal

Order

Comparison, ordering

Order exercise

Write a function that finds the largest element of a List[T]

Show

Text representation - type-safe toString

Show exercise

Write an instance of Show[List[T]]

Semigroup, Monoid

binary operation + (with monoid) identity element

Functor

map, abstracted

Functor laws

There are two rules of the functor club.

//identity law
fa.map(id) == fa
//composition law
fa.map(f).map(g) == fa.map(f.andThen(g))

Functor exercise

Write a function that adds 42 to all elements in a functor

Applicative

(potentially parallel) combination of independent effects, wrapping value in effect

The more real Applicative

Monad

Sequential composition of effects

Monad laws

//left identity
pure(a).flatMap(f) == f(a)
//right identity
fa.flatMap(pure) == fa
//associativity
fa.flatMap(f).flatMap(g) == fa.flatMap(a => f(a).flatMap(g))

ApplicativeError

raising errors, recovering from them

MonadError

Running predicates, transforming errors, un-recovering

Traverse

sequence flips effects

Traverse examples

Traverse vs sequence

fa.traverse(f) == fa.map(f).sequence
fa.sequence == fa.traverse(identity)

Data types

  • NonEmptyList
  • Validated
  • FunctionK
  • OptionT
  • EitherT

NonEmptyList (NEL)

A list that's guaranteed not to be empty

With safe `head`, `tail` operations

Validated

like Either, but with error accumulation

ValidatedNel

type ValidatedNel[+E, +A] = Validated[NonEmptyList[E], A]
Useful for error accumulation, because NEL has a semigroup
type EitherNel[+E, +A] = Either[NonEmptyList[E], A]

Either vs Validated: Applicative

In English

  • Either: if the first one fails, we fail with its error. Otherwise se check the second one.
  • Validated: if both are failed, errors are combined using the semigroup instance.

Applicative builder syntax

No need to call `product`, `map2` or `ap` directly

FunctionK

One more typeclass - Parallel

Not just parallelism

Parallel explained

  • A bi-directional transformation between two isomorphic types with applicatives
  • One has a monad, the other one can't


An applicative that can't be a monad does e.g. parallel execution of IO, or error accumulation like Validated

Parallel applicative syntax

Later - parallel IO

OptionT

Problem:
Solution:

OptionT

EitherT

EitherT usage

Cats-effect

An IO monad for cats (and more)

Cats-effect

  • More data types (IO, concurrency primitives)
  • More instances

IO[A]

  • Just a data structure
  • A pure, immutable value
  • Preserves referential transparency
  • When evaluated, can perform effects before returning a value of type A


Supports synchronous AND asynchronous effects

IO[A] is not a stream

An IO, when evaluated, will do one of these:

  • Eventually complete with a value of type A
  • fail with a Throwable
  • never complete (e.g. IO.never)

Creating IOs

IO - example

IO - more examples

IO - echo name

The "hello world" of IOs

IO - another example

IO in parallel

IO gotchas

How to run?

at end of world, e.g. IOApp

How to run?

SyncIO

IO without async capabilities

Cats-effect typeclasses

  • Bracket
  • Sync
  • LiftIO
  • Async
  • Effect
  • Concurrent
  • ConcurrentEffect

Bracket

Ensuring resource safety - loan pattern in Scala

Bracket - example

Bracket - more

Resource data type

Sync typeclass

Suspend side effects in F[_]

Sync - example

LiftIO

Convert an IO to F[_]

Async

Create asynchronous effects

Async - example

Effect

Run your effect

Concurrent

Start an effect with the ability to cancel

Concurrent example

ConcurrentEffect

Run a (potentially asynchronus) effect synchronously with the ability to cancel it

Cats-effect datatypes

  • IO
  • SyncIO
  • Fiber
  • Ref & friends
  • Clock
  • ContextShift
  • Timer

Ref

Thread-safe, purely functional mutable state

Ref - example

Clock

Provides time

ContextShift

Shifts execution to a thread pool

Usually created with an EC (for IO)

Timer

Schedules delayed tasks (sleep), has a clock

fs2

Stream[F[_], A]

A stream of effects in F[_] producing 0..infinity values of type A

fs2 - example

Building streams

Actually running a stream

brick-store project

Thank you!

Get in touch

@kubukoz | kubukoz@gmail.com