Alexey Novakov
March 06, 2020
43

March 06, 2020

## Transcript

Alexey Novakov, Ultra Tendency, 2020

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

interface”
Book: Functional Programming in Scala (Red Book)

4. Option
Either
List
Future
Map
Set
Stream
Vector
Future
Try
And others …

5. flatMap
unit
a.k.a bind
a.k.a pure
“apply” in 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]
}
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)))
}
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

13. Moreover,
there

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

Left Identity
def f[A](x: A): Monad[A] = ???
flatMap(unit(x))(f) == f(x)
Right Identity
f(x) == flatMap(unit(x))(f)

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)

Associative
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

27. Caveat:

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

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

- specifically constructed for composition
OptionT: Option + Any other Monad
EitherT: Either + Any other Monad
WriterT: Writer + Any other Monad
… others

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