Fantastic Monads and where to find them (2.0)

Jakub Kozłowski - Scala Developer, Scalac

BeeScala | November 24, 2017 | Ljubljana, Slovenia

Motivation

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

Why decouple?

  • Future.successful (repeated)
  • Future.sequence | traverse

  • Easier mocking (in-memory, tests)
  • Easier refactoring to Task 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

Excellent docs at

http://typelevel.org/cats/typeclasses.html

http://typelevel.org/cats/datatypes.html

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._
def mapPlusOne[F[_] : Functor](ints: F[Int]): F[Int] =
  ints.map(_ + 1)
mapPlusOne(List(1, 2, 3)) // List(2, 3, 4)
mapPlusOne(Option(3)) // Some(4): Option[Int]
mapPlusOne(none[Int]) //None: Option[Int]

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]
}

M-word

trait Monad[F[_]] extends Applicative[F]{
  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}

Traverse

trait Traverse[F[_]] extends Functor[F]{
  def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]
}
def sequence[G[_]: Applicative, A](fa: F[G[A]]): G[F[A]]
def x     : List[Future[String]] = ???
x.sequence: Future[List[String]]
                        
x.traverse(f) == x.map(f).sequence
x.sequence = x.traverse(identity)

The code

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 your BeeScala