Alexey Novakov
March 06, 2020
54

March 06, 2020

## Transcript

2. ### "A monad is just a monoid in the category of

endofunctors”

(Red Book)
4. ### Option Either List Future Map Set Stream Vector Future Try

And others … Monads in standard Scala

6. ### class Box[A](v: A) { def flatMap[B](f: A => Box[B]): Box[B]

= f(v) } def map[B](f: A => B): Box[B] = flatMap(a => new Box(f(a))) A Monad modeled as a class minimum set
7. ### scala> new Box(1) res2: Box[Int] = 1 scala> res2.map(_ +

1) res3: Box[Int] = 2 scala> res3.flatMap(i => new Box(1 + i)) res5: Box[Int] = 3
8. ### scala> val l = List(1,2,3) l: List[Int] = List(1, 2,

3) scala> l.map(_ + 1) res0: List[Int] = List(2, 3, 4) scala> l.flatMap(i => List(i + 1)) res1: List[Int] = List(2, 3, 4) List
9. ### val isOn = Some(1) val isBlack = None def makeCoffee:

Option[String] = Some(1) scala> isOn .flatMap(_ => isBlack .flatMap(_ => makeCoffee)) res0: Option[String] = None Option stopped here
10. ### trait Functor[F[_]] { def map[A,B](fa: F[A])(f: A => B): F[B]

} trait Monad[F[_]] extends Functor[F] { def unit[A](a: => A): F[A] def flatMap[A,B](ma: F[A])(f: A => F[B]): F[B] def map[A,B](ma: F[A])(f: A => B): F[B] = flatMap(ma)(a => unit(f(a))) } Generic Monad another abstract interface

12. ### “f” function application in flatMap & map depends on the

concrete Monad instance “f” applied when: - Option[A]: is Some(A) - Either[A, B]: is Right(B) - List[A]: is non-empty - Future[A]: is ready

14. ### Monad law 1. Identity Example def f(x: Int): Option[Int] =

Some(x) scala> Some(1).flatMap(f) == f(1) res0: Boolean = true scala> f(1) == Some(1).flatMap(f) res1: Boolean = true
15. ### Monad law 1. Left Identity def f[A](x: A): Monad[A] =

??? flatMap(unit(x))(f) == f(x) Right Identity f(x) == flatMap(unit(x))(f)
16. ### Monad law 2. Associative Example def f1(a: Int): Option[Int] =

Some(a + 1) def f2(a: Int): Option[Int] = Some(a * 2) scala> Some(1).flatMap(f1).flatMap(f2) res0: Option[Int] = Some(4) scala> Some(1).flatMap(a => f1(a).flatMap(f2)) res1: Option[Int] = Some(4)
17. ### Monad law 2. Associative def f1[A](a: A): Monad[A] def f2[A](a:

A): Monad[A] if x is a Monad instance, flatMap(flatMap(x)(f1))(f2) == flatMap(x)(a => flatMap(f1(a))(f2))
18. ### Functor law 1. Identity map(x)(a => a) == x Example:

map(Some(1))(a => a) == Some(1) the same value returned
19. ### Functor law 2. Associative Example: val f1 = (n: Int)

=> n + 1 val f2 = (n: Int) => n * 2 map(map(Some(1))(f1))(f2) // Some(4) == map(Some(1))(f2 compose f1) // Some(4)
20. ### Functor law 2. Associative map(map(x)(f1))(f2) == map(x)(f2 compose f1) one

by one apply f1 then f2 in one step
21. ### final case class Coffee(name: String) val isOn = Some(1) val

coffeeName = Some("black") val makeCoffee = (name: String) => Some(Coffee(name)) for { _ <- isOn name <- coffeeName coffee <- makeCoffee(name) } yield coffee scala> Op)on[Coﬀee] = Some(Coﬀee(black)) Compose Option
22. ### case class Cluster(pods: Int) def validateNamespace(ns: String): Either[String, Unit] =

Right(()) def clusterExists(ns: String): Either[Cluster, Unit] = Right(()) def createCluster(ns: String, cluster: Cluster): Either[String, Cluster] = Right(Cluster(cluster.pods)) val ns = "my-cluster" for { _ <- validateNamespace(ns) _ <- clusterExists(ns).left.map(c => s"Cluster with \${c.pods} pods already exists") newCluster <- createCluster(ns, Cluster(4)) } yield newCluster Compose Either scala> Either[String,Cluster] = Right(Cluster(4))
23. ### scala> Either[String,Cluster] = LeC( Cluster namespace is not valid name,

choose another name ) def validNamespace(ns: String): Either[String, Unit] = if (ns == "my-cluster") Left( “Cluster namespace is not valid name, choose another name” ) else Right(())
24. ### for {…} yield is a syntactic sugar for a sequence

of calls: flatMap1(… + flatMapN(.. + map(…))) https://en.wikipedia.org/wiki/Powdered_sugar#/media/File:Powdered_Sugar_-_Macro.jpg
25. ### validNamespace("my-cluster") .flatMap(_ => clusterExists(ns) .left .map(c => s"Cluster with \${c.pods}

pods already exists” ).flatMap(_ => createCluster(ns, Cluster(4)) .map(newCluster => newCluster) ) ) Desugared
26. ### for { _ <- validNamespace("my-cluster") _ <- clusterExists(ns) .left.map(c =>

s"Cluster with \${c.pods} pods already exists") newCluster <- createCluster(ns, Cluster(4)) } yield newCluster Sugared

28. ### def validateNamespace(ns: String): Either[String, Unit] def clusterExists(ns: String): Option[Either[String, Cluster]]

def createCluster(ns: String, cluster: Cluster): Either[String, Cluster] Problem for { _ <- validateNamespace(ns) cluster <- clusterExists(ns) updated <- createCluster(ns, cluster) } yield updated two monad stacks
29. ### updated <- createCluster(ns, cluster) ^ <pas)e>:4: error: type mismatch; found

: Either[String,Cluster] required: Cluster cluster <- clusterExists(ns) ^ <pas)e>:3: error: type mismatch; found : Op)on[Nothing] required: scala.u)l.Either[?,?] Op)on[Nothing] <: scala.u)l.Either[?,?]? false

32. ### EitherT *Example: unwrap second stack * from cats.data.EitherT.scala final case

class EitherT[F[_], A, B](value: F[Either[A, B]]) { def flatMap[AA >: A, D]( f: B => EitherT[F, AA, D])( implicit F: Monad[F]): EitherT[F, AA, D] = EitherT(F.flatMap(value) { case l @ Left(_) => F.pure(l.rightCast) case Right(b) => f(b).value }) }
33. ### case class Cluster(pods: Int, updated: Long) def validateNamespace(ns: String): Either[String,

Unit] = Right(()) def clusterExists(ns: String): Option[Either[String, Cluster]] = Some(Right(Cluster(3, System.currentTimeMillis()))) def updateCluster(ns: String, cluster: Cluster): Either[String, Cluster] = Right(Cluster(cluster.pods, System.currentTimeMillis())) Usage
34. ### import cats.implicits._ import cats.data.EitherT val cluster = for { _

<- validateNamespace(ns).toEitherT[Option] cluster<- EitherT(clusterExists(ns)) updated <- updateCluster(ns, cluster).toEitherT[Option] } yield updated scala> cluster.value Some(Right(Cluster(3,1583095558496))) EitherT: Either + Option common ground
35. ### Error case def clusterExists(ns: String): Option[Either[String, Cluster]] = Left("Cluster is

invalid").some scala> cluster.value res4: Op)on[Either[String,Cluster]] = Some(LeC(Cluster is invalid))
36. ### Alexey Novakov email: - alexey.novakov at ultratendency.com - novakov.alex at

gmail.com Blog: https://medium.com/se-notes-by-alexey-novakov https://novakov-alexey.github.io/ Code: https://github.com/novakov-alexey Twitter: @alexey_novakov Thank you! Questions?