Fantastic Monads and where to find them (2.2)

Jakub Kozłowski - Scala Developer, Scalac

Berlin Scala | June 21, 2018 | Berlin, Germany

Motivation

  • Write a simple Akka HTTP app
  • With some nasty validation!
  • Decoupled from futures

Why decouple?

  • Future.successful, Future.sequence | traverse
  • Passing ExecutionContext
  • Future is not really a monad, breaks referential transparency

  • Easier mocking (in-memory, tests)
  • Easier refactoring to Task, IO etc.

Prerequisites

Typeclasses

Functor

Applicative

Monad

Traverse

Cats

typelevel project

"Lightweight, modular, and extensible library for functional programming"

Inspired by Scalaz

Provides type classes, instances and data structures

Using Cats


uber helpful chatroom at

gitter.im/typelevel/cats

Type classes

Example

trait Empty[T]{
  def empty(): T
}

Provides an "empty" instance of T

Type classes

def twoEmpties[T](implicit e: Empty[T]): (T, T) = (e.empty(), e.empty())

implicit val emptyInt: Empty[Int] = new Empty[Int] {
  def empty(): Int = 0
}

scala> val (a, b) = twoEmpties[Int]
a: Int = 0
b: Int = 0

Type classes

scala> val (s1, s2) = twoEmpties[String]

<console>:13: error: could not find
implicit value for parameter e: Empty[String]
       val (s1, s2) = twoEmpties[String]
                                ^

implicit val emptyString: Empty[String] = () => ""

scala> val (s1, s2) = twoEmpties[String]
s1: String = ""
s2: String = ""

Type classes

Conventions

trait Empty[T]{
  def empty(): T
}

object Empty {
  def apply[T](implicit ev: Empty[T]): Empty[T] = ev
}

//syntactic sugar for def twoEmpties[T](implicit ev$1: Empty[T])
def twoEmpties[T : Empty]: (T, T) =
  (Empty[T].empty(), Empty[T].empty())

Standard library samples

//e.g. List(1,2,3).sorted
def sorted[B >: A](implicit ord: Ordering[B])

//e.g. List(1,2,3).sum
def sum[B >: A](implicit num: Numeric[B]): B

Type classes in Cats

Functor

trait Functor[F[_]]{
  def map[T, U](ts: F[T])(f: T => U): F[U]
}
def mapPlusOne[F[_] : Functor](ints: F[Int]): F[Int] = {
  Functor[F].map(ints)(_ + 1)

  //or
  import cats.syntax.functor._
  ints.map(_ + 1)
}
mapPlusOne(List(1, 2, 3)) // List(2, 3, 4)
mapPlusOne(Option(3)) // Some(4): Option[Int]
val f = mapPlusOne((k: String) => k.length) //Functor[String => ?]
f("a") //2

Applicative

trait Applicative[F[_]] extends Functor[F]{ //simplified
  def pure[A](x: A): F[A]

  def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
}
(
  User("Mike").pure[Future],
  findUserF(1),
  findUsersF()
).mapN((u1, u2, users) => u1 :: u2 :: users)

Monad

trait Monad[F[_]] extends Applicative[F]{
  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}
for {
  user <- findUserF(1)
  friends <- findFriendsF(user)
} yield friends

Traverse

trait Traverse[F[_]] extends Functor[F]{
  def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]
}
val strings     : List[Future[String]]
strings.sequence: Future[List[String]]
                        
val findParent             : User => Future[Parent]
val users                  : List[User]
users.traverse(findParentF): Future[List[Parent]]
                        
xs.traverse(f) <-> xs.map(f).sequence
xs.sequence <-> xs.traverse(x => x) <-> xs.traverse(identity)

The code

Enough time?

🅱️onus material:
Monix Task, cats-effect

cats-effect

  • cats module for handling effects
  • provides a referentially transparent IO monad
  • provides concurrency primitives
  • provides type classes like Sync, Async, Effect

monix

  • Scala library for concurrency, async, IO, observables...
  • provides Task, similar to IO


Task/IO are referentially transparent, Future is not.

Code time? Code time.

Reading materials

Typeclasses in Scala (Scalac blog)

Scala with Cats (NEW book from Underscore)

herding cats

typeclasses in cats (cats docs)

datatypes in cats (cats docs)

Thank you!

Slides: kubukoz.github.io/fantastic-monads-slides

Code: github.com/kubukoz/fantastic-monads-code

Questions?



Contact me:

@kubukoz

| kubukoz@gmail.com

| kubukoz.com

Enjoy the rest of the evening