SlideShare a Scribd company logo
1 of 92
Download to read offline
From Functor Composition
to Monad Transformers
© 2018 Hermann Hueck
https://github.com/hermannhueck/monad-transformers
1 / 91
1 / 91
Abstract
In a List[Option[A]] or Future[Option[A]] we want to access the value of type A
conveniently without nested mapping and flatMapping.
We can avoid nested mapping with composed Functors. Functors compose,
but Monads do not! What can we do?
Monad transformers to the rescue!
After going through Functor composition I show how 2 Monads are bolted
together with a Monad transformer and how to use this construct. I
demonstrate this with the Option transformer OptionT and will end up with
best practices.
2 / 91
2 / 91
Agenda
1. Nested Monads in for comprehensions
2. Functors compose.
3. Aside: Creating List[A => B] and List[Option[A => B]]
4. Applicatives compose.
5. Do Monads compose?
6. Monad Transformers - Example OptionT
7. Generic Monad Processing
8. Stacking Monads and Transformers
9. Making the Types Fit
10. Monad Transformers in Cats
11. Best Practices
12. OptionT - Implementation
13. Resources
3 / 91
3 / 91
1. Nested Monads in for
comprehensions
4 / 91
4 / 91
Imports for (all of) Cats
import cats._, cats.data._, cats.implicits._
// import mycats._, transform._, Functor.ops._, Monad.ops._
5 / 91
5 / 91
Imports for my own implementation of
Functor, Applicative, Monad and OptionT
// import cats._, cats.data._, cats.implicits._
import mycats._, transform._, Functor.ops._, Monad.ops._
The same client code works with Cats and with my implementation.
Only the imports need to be changed!
6 / 91
6 / 91
For comprehension for List[Option[Int]]
val loi1: List[Option[Int]] = List(Some(1), None, Some(2), Some(3))
val loi2: List[Option[Int]] = List(Some(1), None, Some(2), Some(3))
val result1: List[Option[Int]] =
for {
oi1: Option[Int] <- loi1
if oi1.isDefined
oi2: Option[Int] <- loi2
if oi2.isDefined
} yield Option(oi1.get * oi2.get)
// result1: List[Option[Int]] = List(Some(1), Some(2),
// Some(3), Some(2), Some(4), Some(6), Some(3), Some(6), Some(9))
7 / 91
7 / 91
Compiler translates for comprehension to
flatMap, map (and withFilter)
val result2 =
loi1
.filter(_.isDefined)
.flatMap { oi1 =>
loi2
.filter(_.isDefined)
.map { oi2 =>
Option(oi1.get * oi2.get)
}
}
// result2: List[Option[Int]] = List(Some(1), Some(2),
// Some(3), Some(2), Some(4), Some(6), Some(3), Some(6), Some(9))
8 / 91
8 / 91
A nested for comprehension
val result3: List[Option[Int]] =
for {
oi1: Option[Int] <- loi1
oi2: Option[Int] <- loi2
} yield for {
i1: Int <- oi1
i2: Int <- oi2
} yield i1 * i2
// result3: List[Option[Int]] = List(Some(1), None,
// Some(2), Some(3), None, None, None, None, Some(2),
// None, Some(4), Some(6), Some(3), None, Some(6), Some(9))
9 / 91
9 / 91
Wanted:
a for comprehension to process Ints nested in 2 contexts
... something like this one:
// This code does not compile!
val result4: List[Option[Int]] =
for {
i1: Int <- loi1
i2: Int <- loi2
} yield i1 * i2
... or like this one:
// How can we create such a ListOption?
val result4: ListOption[Int] =
for {
i1: Int <- loi1
i2: Int <- loi2
} yield i1 * i2
10 / 91
10 / 91
To be usable in a for comprehension our "ListOption" must provide "map",
"flatMap" (and "withFilter") operations. Hence it must be a Functor (for map)
as well a Monad (for flatMap).
Every Monad is also a Functor.
But lets first look at Functors. (It's the simpler part.)
11 / 91
11 / 91
2. Functors compose.
12 / 91
12 / 91
Recap: What is a Functor? (1/2)
// The gist of typeclass Functor
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
13 / 91
13 / 91
Recap: What is a Functor? (2/2)
// Simplified typeclass Functor
trait Functor[F[_]] {
// --- intrinsic abstract functions
def map[A, B](fa: F[A])(f: A => B): F[B]
// --- concrete functions implemented in terms of map
def fmap[A, B](fa: F[A])(f: A => B): F[B] =
map(fa)(f) // alias for map
def compose[G[_]: Functor]: Functor[Lambda[X => F[G[X]]]] = ???
// ... other functions ...
}
14 / 91
14 / 91
Mapping a List[Int] with a Functor[List]
val li = List(1, 2, 3)
val lSquared1 = li.map(x => x * x) // invokes List.map
// lSquared1: List[Int] = List(1, 4, 9)
val lSquared2 = li.fmap(x => x * x) // invokes Functor[List].fmap
// lSquared2: List[Int] = List(1, 4, 9)
val lSquared3 = Functor[List].map(li)(x => x * x) // invokes Functor[List].map
// lSquared3: List[Int] = List(1, 4, 9)
15 / 91
15 / 91
Mapping a List[Option[Int]] with
Functor[List] and Functor[Option]
val loi = List(Some(1), None, Some(2), Some(3))
val loSquared1 = loi.map(oi => oi.map(x => x * x))
// loSquared1: List[Option[Int]] = List(Some(1), None, Some(4), Some(9))
val loSquared2 = Functor[List].map(loi)(oi => Functor[Option].map(oi)(x => x * x))
// loSquared2: List[Option[Int]] = List(Some(1), None, Some(4), Some(9))
val loSquared3 = (Functor[List] compose Functor[Option]).map(loi)(x => x * x)
// loSquared3: List[Option[Int]] = List(Some(1), None, Some(4), Some(9))
val cf = Functor[List] compose Functor[Option]
val loSquared4 = cf.map(loi)(x => x * x)
// loSquared4: List[Option[Int]] = List(Some(1), None, Some(4), Some(9))
16 / 91
16 / 91
Pimping List[Option[A]]
implicit class PimpedListOptionA[A](list: List[Option[A]]) {
val composedFunctor = Functor[List] compose Functor[Option]
def map[B](f: A => B): List[Option[B]] = composedFunctor.map(list)(f)
def fmap[B](f: A => B): List[Option[B]] = composedFunctor.map(list)(f)
}
val loSquared5 = loi.fmap(x => x * x)
// loSquared5: List[Option[Int]] = List(Some(1), None, Some(4), Some(9))
// Attention!!! fmap works but map still doesn't work!!!
val loSquared6 = loi.map(x => x * x)
<console>:25: error: value * is not a member of Option[Int]
val loSquared6 = loi.map(x => x * x)
val loSquared7 = for { x <- loi } yield x * x
<console>:22: error: value * is not a member of Option[Int]
val loSquared7 = for { x <- loi } yield x * x
17 / 91
17 / 91
3. Aside:
Creating List[A => B]
and List[Option[A => B]]
18 / 91
18 / 91
Creating a List[Int => Int]
val lf1_1 = List((x:Int) => x * 1, (x:Int) => x * 2, (x:Int) => x * 3)
// lf1_1: List[Int => Int] = List($$Lambda$..., $$Lambda$..., $$Lambda$...)
val lf1_2 = List((_:Int) * 1, (_:Int) * 2, (_:Int) * 3)
// lf1_2: List[Int => Int] = List($$Lambda$..., $$Lambda$..., $$Lambda$...)
19 / 91
19 / 91
Creating a List[Int => Int] from List[Int]
val lf1_3 = List(1, 2, 3).map(x => (y:Int) => y * x)
// lf1_3: List[Int => Int] = List($$Lambda$..., $$Lambda$..., $$Lambda$...)
val lf1_4 = List(1, 2, 3).map(x => (_:Int) * x)
// lf1_4: List[Int => Int] = List($$Lambda$..., $$Lambda$..., $$Lambda$...)
20 / 91
20 / 91
Creating a List[Option[Int => Int]]
val lf1 = lf1_4
// lf1: List[Int => Int] = List($$Lambda$..., $$Lambda$..., $$Lambda$...)
val lof1 = lf1 map Option.apply
// lof1: List[Option[Int => Int]] =
// List(Some($$Lambda$...), Some($$Lambda$...), Some($$Lambda$...))
21 / 91
21 / 91
4. Applicatives compose.
22 / 91
22 / 91
Recap: What is an Applicative? (1/2)
// The gist of typeclass Applicative
trait Applicative[F[_]] extends Functor[F] {
def pure[A](a: A): F[A]
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
}
23 / 91
23 / 91
Recap: What is an Applicative? (2/2)
// Simplified typeclass Applicative
trait Applicative[F[_]] extends Functor[F] {
// --- intrinsic abstract Applicative methods
def pure[A](a: A): F[A]
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
// --- method implementations in terms of pure and ap
def ap2[A, B, Z](ff: F[(A, B) => Z])(fa: F[A], fb: F[B]): F[Z] = ???
override def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa)
def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) Z): F[Z] = ???
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = ???
def tuple2[A, B](fa: F[A], fb: F[B]): F[(A, B)] = product(fa, fb)
def compose[G[_]: Applicative]: Applicative[Lambda[X => F[G[X]]]] = ???
// ... other functions ...
}
24 / 91
Applying a List[Int => Int]
to a List[Int] with Applicative
val liResult = Applicative[List].ap(lf1)(li)
// liResult: List[Int] = List(1, 2, 3, 2, 4, 6, 3, 6, 9)
25 / 91
24 / 91
25 / 91
Applying a List[Option[Int => Int]]
to a List[Option[Int]] with Applicative
val ca = Applicative[List] compose Applicative[Option]
val loiResult = ca.ap(lof1)(loi)
// loiResult: List[Option[Int]] = List(Some(1), None, Some(2), Some(3),
// Some(2), None, Some(4), Some(6), Some(3), None, Some(6), Some(9))
26 / 91
26 / 91
5. Do Monads compose?
27 / 91
27 / 91
Recap: What is a Monad? (1/2)
// The gist of typeclass Monad
trait Monad[F[_]] extends Applicative[F] {
def pure[A](a: A): F[A]
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}
28 / 91
28 / 91
Recap: What is a Monad? (2/2)
// Simplified typeclass Monad
trait Monad[F[_]] extends Applicative[F] {
// --- intrinsic abstract functions
def pure[A](a: A): F[A]
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
// --- concrete functions implemented in terms of flatMap and pure
override def map[A, B](fa: F[A])(f: A => B): F[B] = flatMap(fa)(a => pure(f(a)))
def fmap[A, B](fa: F[A])(f: A => B): F[B] = map(fa)(f) // alias for map
def flatten[A](ffa: F[F[A]]): F[A] = flatMap(ffa)(identity)
override def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = ???
// ... other functions ...
}
29 / 91
29 / 91
Do Monads compose?
val cf = Functor[List] compose Functor[Option]
// cf: cats.Functor[[α]List[Option[α]]] = cats.Functor$$anon$1@386ed52a
val ca = Applicative[List] compose Applicative[Option]
// ca: cats.Applicative[[α]List[Option[α]]] = cats.Alternative$$anon$1@69102316
val cm = Monad[List] compose Monad[Option]
// cm: cats.Applicative[[α]List[Option[α]]] = cats.Alternative$$anon$1@48b5e9dd
30 / 91
30 / 91
Trying to compose 2 generic Monads
We cannot implement flatMap without knowing the higher kinded type of the
inner Monad.
// Hypothetical composition
def composeFWithGImpossible[F[_]: Monad, G[_]: Monad] = {
type Composed[A] = F[G[A]]
new Monad[Composed] {
def pure[A](a: A): Composed[A] = Monad[F].pure(Monad[G].pure(a))
def flatMap[A, B](fa: Composed[A])(f: A => Composed[B]): Composed[B] = ???
// !!! Problem! How do we write flatMap? Impossible to implement
// !!! generically without knowledge of the type of the inner Monad!
}
}
31 / 91
31 / 91
Trying to compose 1 generic and 1 concrete
Monad
Knowing the higher kinded type of the inner Monad (Option) we can
implement flatMap.
def composeFWithOption[F[_]: Monad] = {
type Composed[A] = F[Option[A]]
new Monad[Composed] {
def pure[A](a: A): Composed[A] = Monad[F].pure(Monad[Option].pure(a))
def flatMap[A, B](fOptA: Composed[A])(f: A => Composed[B]): Composed[B] =
Monad[F].flatMap(fOptA) {
case None => Monad[F].pure(Option.empty[B])
case Some(a) => f(a)
}
}
}
32 / 91
32 / 91
Do Monads compose?
Functors compose.
Applicatives compose.
Monads do NOT compose!
33 / 91
33 / 91
Do Monads compose?
Functors compose.
Applicatives compose.
Monads do NOT compose!
Solution: Monad Transformers
34 / 91
34 / 91
Do Monads compose?
Functors compose.
Applicatives compose.
Monads do NOT compose!
Solution: Monad Transformers
Monad Transformers are some kind of composition mechanism for
Monads.
35 / 91
35 / 91
Do Monads compose?
Functors compose.
Applicatives compose.
Monads do NOT compose!
Solution: Monad Transformers
Monad Transformers are some kind of composition mechanism for
Monads.
The inner Monad must be concrete, e.g. Option or Either.
The outer Monad is left abstract. F[_]: Monad
36 / 91
36 / 91
6. Monad Transformers -
Example OptionT
37 / 91
37 / 91
What is OptionT?
OptionT is a generic case class which encapsulates an F[Option[A]].
F is a place holder for any Monad: List, Future, Either[A, ?], Id etc.
A Monad instance for OptionT (implementing pure and flatMap) must be
provided implicitly in order to allow flatMapping over OptionT.
final case class OptionT[F[_]: Monad, A](value: F[Option[A]]) {
// ...
}
object OptionT {
implicit def monad[F[_]]: Monad[F]: Monad[OptionT[F, ?]] =
new Monad[OptionT[F, ?]] {
override def pure[A](a: A) = ???
override def flatMap[A, B](fa)(f) = ???
}
}
38 / 91
38 / 91
Creating an OptionT[List, Int]
to encapsulate a List[Option[Int]]
val loi = List(Some(1), None, Some(2), Some(3))
// loi: List[Option[Int]] = List(Some(1), None, Some(2), Some(3))
val otli = OptionT[List, Int](loi)
// otli: cats.data.OptionT[List,Int] = OptionT(List(Some(1), None, Some(2), Some(3)))
otli.value // same as: loi
// res6: List[Option[Int]] = List(Some(1), None, Some(2), Some(3))
39 / 91
39 / 91
FlatMapping an OptionT[List, Int]
flatMap takes a function of type: A => OptionT[F, B]
In our case the type is: Int => OptionT[List, String]
def fillListWith(x: Int): List[Option[String]] = List.fill(x)(Option(x.toString))
val otliFlatMapped = otli.flatMap(x => OptionT[List, String](fillListWith(x)))
// otliFlatMapped: cats.data.OptionT[List,String] =
// OptionT(List(Some(1), None, Some(2), Some(2), Some(3), Some(3), Some(3)))
otliFlatMapped.value
// res7: List[Option[String]] =
// List(Some(1), None, Some(2), Some(2), Some(3), Some(3), Some(3))
40 / 91
40 / 91
Convenience function flatMapF
flatMapF takes a function of type: A => F[Option[B]]
In our case the type is: Int => List[Option[String]]
def fillListWith(x: Int): List[Option[String]] = List.fill(x)(Option(x.toString))
val otliFlatMappedF = otli.flatMapF(x => fillListWith(x))
// otliFlatMappedF: cats.data.OptionT[List,String] =
// OptionT(List(Some(1), None, Some(2), Some(2), Some(3), Some(3), Some(3)))
otliFlatMappedF.value
// res8: List[Option[String]] =
// List(Some(1), None, Some(2), Some(2), Some(3), Some(3), Some(3))
41 / 91
41 / 91
Mapping an OptionT[List, Int]
val otliMapped = Monad[OptionT[List, ?]].map(otli) { _.toString + "!" }
// otliMapped: cats.data.OptionT[[+A]List[A],String] =
// OptionT(List(Some(1!), None, Some(2!), Some(3!)))
otliMapped.value
// res9: List[Option[String]] = List(Some(1!), None, Some(2!), Some(3!))
42 / 91
42 / 91
OptionT.{isDefined, isEmpty, getOrElse, fold}
otli.isDefined
// res10: List[Boolean] = List(true, false, true, true)
otli.isEmpty
// res11: List[Boolean] = List(false, true, false, false)
otli.getOrElse(42)
// res12: List[Int] = List(1, 42, 2, 3)
otli.fold(42.0)(_.toDouble)
// res13: List[Int] = List(1.0, 42.0, 2.0, 3.0)
43 / 91
43 / 91
For comprehension with OptionT[List, Int]
encapsulating List[Option[Int]]
val result4: OptionT[List, Int] =
for {
x <- otli
y <- otli
} yield x * y
// result4: cats.data.OptionT[List,Int] =
// OptionT(List(Some(1), None, Some(2), Some(3), None, Some(2),
// None, Some(4), Some(6), Some(3), None, Some(6), Some(9)))
result4.value
// res13: List[Option[Int]] =
// List(Some(1), None, Some(2), Some(3), None, Some(2),
// None, Some(4), Some(6), Some(3), None, Some(6), Some(9))
44 / 91
44 / 91
7. Generic Monad Processing
45 / 91
45 / 91
Using processIntMonads with OptionT
OptionT is a Monad which encapsulates two other Monads.
Hence an OptionT can be passed to a function accepting any Monad F[Int].
def processIntMonads[F[_]: Monad](monad1: F[Int], monad2: F[Int]): F[Int] =
for {
x <- monad1
y <- monad2
} yield x * y
val result5 = processIntMonads(otli, otli)
// result5: cats.data.OptionT[List,Int] = OptionT(List(
// Some(1), None, Some(2), Some(3), None, Some(2),
// None, Some(4), Some(6), Some(3), None, Some(6), Some(9)))
result5.value
// res14: List[Option[Int]] = List(
// Some(1), None, Some(2), Some(3), None, Some(2),
// None, Some(4), Some(6), Some(3), None, Some(6), Some(9))
46 / 91
46 / 91
Using processIntMonads with other Monads
List
Vector
Option
Future
processIntMonads(List(1, 2, 3), List(10, 20, 30))
// res16: List[Int] = List(10, 20, 30, 20, 40, 60, 30, 60, 90)
processIntMonads(Vector(1, 2, 3), Vector(10, 20, 30))
// res17: scala.collection.immutable.Vector[Int] = Vector(10, 20, 30, 20, 40, 60, 30, 60, 90)
processIntMonads(Option(5), Option(5))
// res18: Option[Int] = Some(25)
val fi = processIntMonads(Future(5), Future(5))
// fi: scala.concurrent.Future[Int] = Future(<not completed>)
Await.ready(fi, 1.second)
fi
// res20: scala.concurrent.Future[Int] = Future(Success(25))
47 / 91
47 / 91
Using processIntMonads with other OptionT-
Monads
OptionT[Vector, Int]
OptionT[Option, Int]
OptionT[Future, Int]
val otvi = OptionT[Vector, Int](Vector(Option(3), Option(5)))
processIntMonads(otvi, otvi).value
// res21: Vector[Option[Int]] = Vector(Some(9), Some(15), Some(15), Some(25))
val otoi = OptionT[Option, Int](Option(Option(5)))
processIntMonads(otoi, otoi).value
// res22: Option[Option[Int]] = Some(Some(25))
val otfi = processIntMonads(OptionT(Future(Option(5))), OptionT(Future(Option(5))))
Await.ready(otfi.value, 1.second)
otfi.value
// res23: scala.concurrent.Future[Option[Int]] = Future(Success(Some(25)))
48 / 91
48 / 91
8. Stacking Monads and
Transformers
49 / 91
49 / 91
Stacking 3 Monads with 2 transformers
A database query should be async -> use a Future[???]
Accessing the DB may give you an error -> Future[Either[String, ???]]
The searched entity may not be there -> Future[Either[String, Option[???]]]
With EitherT and OptionT we can stack the 3 Monads together.
def compute[A]: A => Future[Either[String, Option[A]]] =
input => Future(Right(Some(input)))
def stackMonads[A]: A => OptionT[EitherT[Future, String, ?], A] =
input => OptionT(EitherT(compute(input)))
val stackedResult: OptionT[EitherT[Future, String, ?], Int] =
for {
a <- stackMonads(10)
b <- stackMonads(32)
} yield a + b
val future: Future[Either[String, Option[Int]]] = stackedResult.value.value
val result: Either[String, Option[Int]] = Await.result(future, 3.seconds)
println(result.right.get) // 42
50 / 91
50 / 91
9. Making the Types Fit
51 / 91
51 / 91
When types don't fit ...
type Nickname = String
type Name = String
type Age = Int
final case class User(name: Name, age: Age, nickname: Nickname)
// return types of these functions are incompatible
def getUser(nickname: Nickname): Future[Option[User]] = ???
def getAge(user: User): Future[Age] = ???
def getName(user: User): Option[Name] = ???
52 / 91
52 / 91
... make them fit
... the classical way
def getNameAge(nickname: Nickname): Future[Option[(Name, Age)]] =
(for {
user <- OptionT(getUser(nickname))
age <- OptionT(getAge(user).map(Option(_)))
name <- OptionT(Future(getName(user)))
} yield (name, age)).value
53 / 91
53 / 91
... make them fit
... the classical way
def getNameAge(nickname: Nickname): Future[Option[(Name, Age)]] =
(for {
user <- OptionT(getUser(nickname))
age <- OptionT(getAge(user).map(Option(_)))
name <- OptionT(Future(getName(user)))
} yield (name, age)).value
... or with OptionT.liftF and OptionT.fromOption.
def getNameAge(nickname: Nickname): Future[Option[(Name, Age)]] =
(for {
user <- OptionT(getUser(nickname))
age <- OptionT.liftF(getAge(user))
name <- OptionT.fromOption(getName(user))
} yield (name, age)).value
54 / 91
54 / 91
Compare the solution without OptionT
def getNameAge(nickname: Nickname): Future[Option[(Name, Age)]] =
for {
maybeUser <- getUser(nickname)
if maybeUser.isDefined
user = maybeUser.get
maybeAge <- getAge(user).map(Option(_))
maybeName <- Future(getName(user))
} yield for {
name <- maybeName
age <- maybeAge
} yield (name, age)
55 / 91
55 / 91
10. Monad Transformers in
Cats
cats.data.OptionT for Option
cats.data.EitherT for Either
cats.data.IdT for Id
cats.data.WriterT for Writer
cats.data.ReaderT for Reader (ReaderT is an alias for Kleisli)
cats.data.StateT for State
56 / 91
56 / 91
11. Best Practices
57 / 91
57 / 91
Don't expose Monad transformers to your API!
58 / 91
58 / 91
Don't expose Monad transformers to your API!
This would make your API harder to understand.
59 / 91
59 / 91
Don't expose Monad transformers to your API!
This would make your API harder to understand.
The user of your API may not know what a Monad transformer is.
60 / 91
60 / 91
Don't expose Monad transformers to your API!
This would make your API harder to understand.
The user of your API may not know what a Monad transformer is.
Just call transformer.value before you expose it.
61 / 91
61 / 91
Don't expose Monad transformers to your API!
This would make your API harder to understand.
The user of your API may not know what a Monad transformer is.
Just call transformer.value before you expose it.
Thus you expose List[Option[A]] instead of OptionT[List, A].
62 / 91
62 / 91
Don't expose Monad transformers to your API!
This would make your API harder to understand.
The user of your API may not know what a Monad transformer is.
Just call transformer.value before you expose it.
Thus you expose List[Option[A]] instead of OptionT[List, A].
Or you expose Future[Option[A]] instead of OptionT[Future, A].
See example
63 / 91
63 / 91
Don't stack too many Monads!
64 / 91
64 / 91
Don't stack too many Monads!
A Monad transformer is just another Monad (wrapping 2 Monads).
65 / 91
65 / 91
Don't stack too many Monads!
A Monad transformer is just another Monad (wrapping 2 Monads).
You can wrap 3 Monads with 2 transformers (= 5 Monads).
66 / 91
66 / 91
Don't stack too many Monads!
A Monad transformer is just another Monad (wrapping 2 Monads).
You can wrap 3 Monads with 2 transformers (= 5 Monads).
You can wrap 4 Monads with 3 transformers (= 7 Monads), etc.
67 / 91
67 / 91
Don't stack too many Monads!
A Monad transformer is just another Monad (wrapping 2 Monads).
You can wrap 3 Monads with 2 transformers (= 5 Monads).
You can wrap 4 Monads with 3 transformers (= 7 Monads), etc.
Too many stacked transformers do not make your code clearer.
68 / 91
68 / 91
Don't stack too many Monads!
A Monad transformer is just another Monad (wrapping 2 Monads).
You can wrap 3 Monads with 2 transformers (= 5 Monads).
You can wrap 4 Monads with 3 transformers (= 7 Monads), etc.
Too many stacked transformers do not make your code clearer.
Too many stacked transformers may degrade performance.
69 / 91
69 / 91
Don't stack too many Monads!
A Monad transformer is just another Monad (wrapping 2 Monads).
You can wrap 3 Monads with 2 transformers (= 5 Monads).
You can wrap 4 Monads with 3 transformers (= 7 Monads), etc.
Too many stacked transformers do not make your code clearer.
Too many stacked transformers may degrade performance.
Structures consume heap.
70 / 91
70 / 91
Don't stack too many Monads!
A Monad transformer is just another Monad (wrapping 2 Monads).
You can wrap 3 Monads with 2 transformers (= 5 Monads).
You can wrap 4 Monads with 3 transformers (= 7 Monads), etc.
Too many stacked transformers do not make your code clearer.
Too many stacked transformers may degrade performance.
Structures consume heap.
Does it really slow down your app? Maybe IO is the bottleneck?
Benchmark if necessary!
71 / 91
71 / 91
Don't stack too many Monads!
A Monad transformer is just another Monad (wrapping 2 Monads).
You can wrap 3 Monads with 2 transformers (= 5 Monads).
You can wrap 4 Monads with 3 transformers (= 7 Monads), etc.
Too many stacked transformers do not make your code clearer.
Too many stacked transformers may degrade performance.
Structures consume heap.
Does it really slow down your app? Maybe IO is the bottleneck?
Benchmark if necessary!
Consider other approaches:
72 / 91
72 / 91
Don't stack too many Monads!
A Monad transformer is just another Monad (wrapping 2 Monads).
You can wrap 3 Monads with 2 transformers (= 5 Monads).
You can wrap 4 Monads with 3 transformers (= 7 Monads), etc.
Too many stacked transformers do not make your code clearer.
Too many stacked transformers may degrade performance.
Structures consume heap.
Does it really slow down your app? Maybe IO is the bottleneck?
Benchmark if necessary!
Consider other approaches:
Free Monads
73 / 91
73 / 91
Don't stack too many Monads!
A Monad transformer is just another Monad (wrapping 2 Monads).
You can wrap 3 Monads with 2 transformers (= 5 Monads).
You can wrap 4 Monads with 3 transformers (= 7 Monads), etc.
Too many stacked transformers do not make your code clearer.
Too many stacked transformers may degrade performance.
Structures consume heap.
Does it really slow down your app? Maybe IO is the bottleneck?
Benchmark if necessary!
Consider other approaches:
Free Monads
Tagless Final
74 / 91
74 / 91
Don't stack too many Monads!
A Monad transformer is just another Monad (wrapping 2 Monads).
You can wrap 3 Monads with 2 transformers (= 5 Monads).
You can wrap 4 Monads with 3 transformers (= 7 Monads), etc.
Too many stacked transformers do not make your code clearer.
Too many stacked transformers may degrade performance.
Structures consume heap.
Does it really slow down your app? Maybe IO is the bottleneck?
Benchmark if necessary!
Consider other approaches:
Free Monads
Tagless Final
Or: Go back to nested for comprehensions.
75 / 91
75 / 91
Don't stack too many Monads!
A Monad transformer is just another Monad (wrapping 2 Monads).
You can wrap 3 Monads with 2 transformers (= 5 Monads).
You can wrap 4 Monads with 3 transformers (= 7 Monads), etc.
Too many stacked transformers do not make your code clearer.
Too many stacked transformers may degrade performance.
Structures consume heap.
Does it really slow down your app? Maybe IO is the bottleneck?
Benchmark if necessary!
Consider other approaches:
Free Monads
Tagless Final
Or: Go back to nested for comprehensions.
Follow the "Principle of least power"!
76 / 91
76 / 91
12. OptionT - Implementation
77 / 91
77 / 91
ListOption - implementation
final case class ListOption[A](value: List[Option[A]]) {
def map[B](f: A => B): ListOption[B] =
ListOption(value.map(_ map f))
def flatMap[B](f: A => ListOption[B]): ListOption[B] =
ListOption(
value.flatMap {
case None => List(Option.empty[B])
case Some(a) => f(a).value
}
)
def flatMapF[B](f: A => List[Option[B]]): ListOption[B] =
flatMap(a => ListOption(f(a)))
def isDefined: List[Boolean] = value.map(_.isDefined)
def isEmpty: List[Boolean] = value.map(_.isEmpty)
def getOrElse(default: => A): List[A] = value.map(_.getOrElse(default))
}
object ListOption {
implicit def monad: Monad[ListOption] = new Monad[ListOption] {
override def pure[A](a: A): ListOption[A] = ListOption(List(Option(a)))
override def flatMap[A, B](fa: ListOption[A])
(f: A => ListOption[B]): ListOption[B] = fa flatMap f
}
}
78 / 91
78 / 91
Abstracting over List gives you OptionT.
79 / 91
79 / 91
Abstracting over List gives you OptionT.
Parameterize ListOption with type constructor: ListOption[F[_], A]
80 / 91
80 / 91
Abstracting over List gives you OptionT.
Parameterize ListOption with type constructor: ListOption[F[_], A]
F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A]
81 / 91
81 / 91
Abstracting over List gives you OptionT.
Parameterize ListOption with type constructor: ListOption[F[_], A]
F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A]
Save an instance of Monad F in a value: val F = Monad[F]
82 / 91
82 / 91
Abstracting over List gives you OptionT.
Parameterize ListOption with type constructor: ListOption[F[_], A]
F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A]
Save an instance of Monad F in a value: val F = Monad[F]
Replace every occurence of List with type constructor F.
83 / 91
83 / 91
Abstracting over List gives you OptionT.
Parameterize ListOption with type constructor: ListOption[F[_], A]
F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A]
Save an instance of Monad F in a value: val F = Monad[F]
Replace every occurence of List with type constructor F.
Replace every ListOption[A] with ListOption[F, A].
84 / 91
84 / 91
Abstracting over List gives you OptionT.
Parameterize ListOption with type constructor: ListOption[F[_], A]
F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A]
Save an instance of Monad F in a value: val F = Monad[F]
Replace every occurence of List with type constructor F.
Replace every ListOption[A] with ListOption[F, A].
Replace invocations of map/flatMap on List by invocations on Monad F.
85 / 91
85 / 91
Abstracting over List gives you OptionT.
Parameterize ListOption with type constructor: ListOption[F[_], A]
F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A]
Save an instance of Monad F in a value: val F = Monad[F]
Replace every occurence of List with type constructor F.
Replace every ListOption[A] with ListOption[F, A].
Replace invocations of map/flatMap on List by invocations on Monad F.
Replace invocations of List.apply by invocations of F.pure.
86 / 91
86 / 91
Abstracting over List gives you OptionT.
Parameterize ListOption with type constructor: ListOption[F[_], A]
F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A]
Save an instance of Monad F in a value: val F = Monad[F]
Replace every occurence of List with type constructor F.
Replace every ListOption[A] with ListOption[F, A].
Replace invocations of map/flatMap on List by invocations on Monad F.
Replace invocations of List.apply by invocations of F.pure.
Rename ListOption to OptionT.
87 / 91
87 / 91
OptionT - implementation
final case class OptionT[F[_]: Monad, A](value: F[Option[A]]) {
val F = Monad[F]
def map[B](f: A => B): OptionT[F, B] =
OptionT(F.map(value)(_ map f))
def flatMap[B](f: A => OptionT[F, B]): OptionT[F, B] =
OptionT(
F.flatMap(value) {
case None => F.pure(Option.empty[B])
case Some(a) => f(a).value
}
)
def flatMapF[B](f: A => F[Option[B]]): OptionT[F, B] =
flatMap(a => OptionT(f(a)))
def isDefined: F[Boolean] = F.map(value)(_.isDefined)
def isEmpty: F[Boolean] = F.map(value)(_.isEmpty)
def getOrElse(default: => A): F[A] = F.map(value)(_.getOrElse(default))
}
object OptionT {
implicit def monad[F[_]: Monad]: Monad[OptionT[F, ?]] =
new Monad[OptionT[F, ?]] {
override def pure[A](a: A): OptionT[F, A] =
OptionT(Monad[F].pure(Option(a)))
override def flatMap[A, B](fa: OptionT[F, A])
(f: A => OptionT[F, B]): OptionT[F, B] = fa flatMap f
}
}
88 / 91
88 / 91
13. Resources
89 / 91
89 / 91
Resources
Code and Slides of this Talk:
https://github.com/hermannhueck/monad-transformers
"Scala with Cats"
Book by Noel Welsh and Dave Gurnell
https://underscore.io/books/scala-with-cats/
"Herding Cats, day 10: Stacking Future and Either"
Blogpost by eed3si9n (Eugene Yokota)
http://eed3si9n.com/herding-cats/stacking-future-and-either.html
"Monad transformers down to earth"
Talk by Gabriele Petronella at Scala Days Copenhagen 2017
https://www.youtube.com/watch?v=jd5e71nFEZM
"FSiS Part 7 - OptionT transformer"
Live coding video tutorial by Michael Pilquist, 2015
https://www.youtube.com/watch?v=ZNUTMabdgzo
90 / 91
90 / 91
Thanks for Listening
Q & A
https://github.com/hermannhueck/monad-transformers
91 / 91
91 / 91

More Related Content

What's hot

Functional programming in Python
Functional programming in PythonFunctional programming in Python
Functional programming in PythonColin Su
 
What is Elm and Why Should I care?
What is Elm and Why Should I care?What is Elm and Why Should I care?
What is Elm and Why Should I care?Thomas Groshong
 
Abstracting over Execution with Higher Kinded Types
Abstracting over Execution with Higher Kinded TypesAbstracting over Execution with Higher Kinded Types
Abstracting over Execution with Higher Kinded TypesPhilip Schwarz
 
EcmaScript unchained
EcmaScript unchainedEcmaScript unchained
EcmaScript unchainedEduard Tomàs
 
Functions in C++
Functions in C++Functions in C++
Functions in C++home
 
Python programming workshop session 4
Python programming workshop session 4Python programming workshop session 4
Python programming workshop session 4Abdul Haseeb
 
Introduction to functional programming using Ocaml
Introduction to functional programming using OcamlIntroduction to functional programming using Ocaml
Introduction to functional programming using Ocamlpramode_ce
 
46630497 fun-pointer-1
46630497 fun-pointer-146630497 fun-pointer-1
46630497 fun-pointer-1AmIt Prasad
 
F# Presentation
F# PresentationF# Presentation
F# Presentationmrkurt
 
C++ Overview
C++ OverviewC++ Overview
C++ Overviewkelleyc3
 
FParsec Hands On - F#unctional Londoners 2014
FParsec Hands On -  F#unctional Londoners 2014FParsec Hands On -  F#unctional Londoners 2014
FParsec Hands On - F#unctional Londoners 2014Phillip Trelford
 
C++ functions presentation by DHEERAJ KATARIA
C++ functions presentation by DHEERAJ KATARIAC++ functions presentation by DHEERAJ KATARIA
C++ functions presentation by DHEERAJ KATARIADheeraj Kataria
 
Atomically { Delete Your Actors }
Atomically { Delete Your Actors }Atomically { Delete Your Actors }
Atomically { Delete Your Actors }John De Goes
 
API design: using type classes and dependent types
API design: using type classes and dependent typesAPI design: using type classes and dependent types
API design: using type classes and dependent typesbmlever
 
Introduction to Monads in Scala (1)
Introduction to Monads in Scala (1)Introduction to Monads in Scala (1)
Introduction to Monads in Scala (1)stasimus
 
Advance python programming
Advance python programming Advance python programming
Advance python programming Jagdish Chavan
 

What's hot (20)

Functional programming in Python
Functional programming in PythonFunctional programming in Python
Functional programming in Python
 
What is Elm and Why Should I care?
What is Elm and Why Should I care?What is Elm and Why Should I care?
What is Elm and Why Should I care?
 
functions
functionsfunctions
functions
 
Abstracting over Execution with Higher Kinded Types
Abstracting over Execution with Higher Kinded TypesAbstracting over Execution with Higher Kinded Types
Abstracting over Execution with Higher Kinded Types
 
EcmaScript unchained
EcmaScript unchainedEcmaScript unchained
EcmaScript unchained
 
C++ presentation
C++ presentationC++ presentation
C++ presentation
 
Functions in C++
Functions in C++Functions in C++
Functions in C++
 
Python programming workshop session 4
Python programming workshop session 4Python programming workshop session 4
Python programming workshop session 4
 
Introduction to functional programming using Ocaml
Introduction to functional programming using OcamlIntroduction to functional programming using Ocaml
Introduction to functional programming using Ocaml
 
46630497 fun-pointer-1
46630497 fun-pointer-146630497 fun-pointer-1
46630497 fun-pointer-1
 
F# Presentation
F# PresentationF# Presentation
F# Presentation
 
Stack queue
Stack queueStack queue
Stack queue
 
C++ Overview
C++ OverviewC++ Overview
C++ Overview
 
FParsec Hands On - F#unctional Londoners 2014
FParsec Hands On -  F#unctional Londoners 2014FParsec Hands On -  F#unctional Londoners 2014
FParsec Hands On - F#unctional Londoners 2014
 
C++ functions presentation by DHEERAJ KATARIA
C++ functions presentation by DHEERAJ KATARIAC++ functions presentation by DHEERAJ KATARIA
C++ functions presentation by DHEERAJ KATARIA
 
Atomically { Delete Your Actors }
Atomically { Delete Your Actors }Atomically { Delete Your Actors }
Atomically { Delete Your Actors }
 
C++ Functions
C++ FunctionsC++ Functions
C++ Functions
 
API design: using type classes and dependent types
API design: using type classes and dependent typesAPI design: using type classes and dependent types
API design: using type classes and dependent types
 
Introduction to Monads in Scala (1)
Introduction to Monads in Scala (1)Introduction to Monads in Scala (1)
Introduction to Monads in Scala (1)
 
Advance python programming
Advance python programming Advance python programming
Advance python programming
 

Similar to From Functor Composition to Monad Transformers

Fp in scala with adts
Fp in scala with adtsFp in scala with adts
Fp in scala with adtsHang Zhao
 
Monads and friends demystified
Monads and friends demystifiedMonads and friends demystified
Monads and friends demystifiedAlessandro Lacava
 
Functions, Types, Programs and Effects
Functions, Types, Programs and EffectsFunctions, Types, Programs and Effects
Functions, Types, Programs and EffectsRaymond Roestenburg
 
Monoids - Part 2 - with examples using Scalaz and Cats
Monoids - Part 2 - with examples using Scalaz and CatsMonoids - Part 2 - with examples using Scalaz and Cats
Monoids - Part 2 - with examples using Scalaz and CatsPhilip Schwarz
 
Monad Transformers In The Wild
Monad Transformers In The WildMonad Transformers In The Wild
Monad Transformers In The WildStackMob Inc
 
Fp in scala with adts part 2
Fp in scala with adts part 2Fp in scala with adts part 2
Fp in scala with adts part 2Hang Zhao
 
Scala collection methods flatMap and flatten are more powerful than monadic f...
Scala collection methods flatMap and flatten are more powerful than monadic f...Scala collection methods flatMap and flatten are more powerful than monadic f...
Scala collection methods flatMap and flatten are more powerful than monadic f...Philip Schwarz
 
하스켈 프로그래밍 입문 4
하스켈 프로그래밍 입문 4하스켈 프로그래밍 입문 4
하스켈 프로그래밍 입문 4Kwang Yul Seo
 
Advance Scala - Oleg Mürk
Advance Scala - Oleg MürkAdvance Scala - Oleg Mürk
Advance Scala - Oleg MürkPlanet OS
 
The Essence of the Iterator Pattern
The Essence of the Iterator PatternThe Essence of the Iterator Pattern
The Essence of the Iterator PatternEric Torreborre
 
The Essence of the Iterator Pattern (pdf)
The Essence of the Iterator Pattern (pdf)The Essence of the Iterator Pattern (pdf)
The Essence of the Iterator Pattern (pdf)Eric Torreborre
 
Stanfy MadCode Meetup #9: Functional Programming 101 with Swift
Stanfy MadCode Meetup #9: Functional Programming 101 with SwiftStanfy MadCode Meetup #9: Functional Programming 101 with Swift
Stanfy MadCode Meetup #9: Functional Programming 101 with SwiftStanfy
 
Monads from Definition
Monads from DefinitionMonads from Definition
Monads from DefinitionDierk König
 
How to start functional programming (in Scala): Day1
How to start functional programming (in Scala): Day1How to start functional programming (in Scala): Day1
How to start functional programming (in Scala): Day1Taisuke Oe
 

Similar to From Functor Composition to Monad Transformers (20)

Monads do not Compose
Monads do not ComposeMonads do not Compose
Monads do not Compose
 
Fp in scala with adts
Fp in scala with adtsFp in scala with adts
Fp in scala with adts
 
Monads and friends demystified
Monads and friends demystifiedMonads and friends demystified
Monads and friends demystified
 
Functions, Types, Programs and Effects
Functions, Types, Programs and EffectsFunctions, Types, Programs and Effects
Functions, Types, Programs and Effects
 
Monoids - Part 2 - with examples using Scalaz and Cats
Monoids - Part 2 - with examples using Scalaz and CatsMonoids - Part 2 - with examples using Scalaz and Cats
Monoids - Part 2 - with examples using Scalaz and Cats
 
Functor Composition
Functor CompositionFunctor Composition
Functor Composition
 
08. haskell Functions
08. haskell Functions08. haskell Functions
08. haskell Functions
 
Monad Transformers In The Wild
Monad Transformers In The WildMonad Transformers In The Wild
Monad Transformers In The Wild
 
Fp in scala with adts part 2
Fp in scala with adts part 2Fp in scala with adts part 2
Fp in scala with adts part 2
 
Scala collection methods flatMap and flatten are more powerful than monadic f...
Scala collection methods flatMap and flatten are more powerful than monadic f...Scala collection methods flatMap and flatten are more powerful than monadic f...
Scala collection methods flatMap and flatten are more powerful than monadic f...
 
10. haskell Modules
10. haskell Modules10. haskell Modules
10. haskell Modules
 
Practical cats
Practical catsPractical cats
Practical cats
 
하스켈 프로그래밍 입문 4
하스켈 프로그래밍 입문 4하스켈 프로그래밍 입문 4
하스켈 프로그래밍 입문 4
 
09. haskell Context
09. haskell Context09. haskell Context
09. haskell Context
 
Advance Scala - Oleg Mürk
Advance Scala - Oleg MürkAdvance Scala - Oleg Mürk
Advance Scala - Oleg Mürk
 
The Essence of the Iterator Pattern
The Essence of the Iterator PatternThe Essence of the Iterator Pattern
The Essence of the Iterator Pattern
 
The Essence of the Iterator Pattern (pdf)
The Essence of the Iterator Pattern (pdf)The Essence of the Iterator Pattern (pdf)
The Essence of the Iterator Pattern (pdf)
 
Stanfy MadCode Meetup #9: Functional Programming 101 with Swift
Stanfy MadCode Meetup #9: Functional Programming 101 with SwiftStanfy MadCode Meetup #9: Functional Programming 101 with Swift
Stanfy MadCode Meetup #9: Functional Programming 101 with Swift
 
Monads from Definition
Monads from DefinitionMonads from Definition
Monads from Definition
 
How to start functional programming (in Scala): Day1
How to start functional programming (in Scala): Day1How to start functional programming (in Scala): Day1
How to start functional programming (in Scala): Day1
 

More from Hermann Hueck

What's new in Scala 2.13?
What's new in Scala 2.13?What's new in Scala 2.13?
What's new in Scala 2.13?Hermann Hueck
 
Type Classes in Scala and Haskell
Type Classes in Scala and HaskellType Classes in Scala and Haskell
Type Classes in Scala and HaskellHermann Hueck
 
Reactive Access to MongoDB from Scala
Reactive Access to MongoDB from ScalaReactive Access to MongoDB from Scala
Reactive Access to MongoDB from ScalaHermann Hueck
 
Reactive Access to MongoDB from Java 8
Reactive Access to MongoDB from Java 8Reactive Access to MongoDB from Java 8
Reactive Access to MongoDB from Java 8Hermann Hueck
 
Implementing a many-to-many Relationship with Slick
Implementing a many-to-many Relationship with SlickImplementing a many-to-many Relationship with Slick
Implementing a many-to-many Relationship with SlickHermann Hueck
 

More from Hermann Hueck (7)

A Taste of Dotty
A Taste of DottyA Taste of Dotty
A Taste of Dotty
 
What's new in Scala 2.13?
What's new in Scala 2.13?What's new in Scala 2.13?
What's new in Scala 2.13?
 
Pragmatic sbt
Pragmatic sbtPragmatic sbt
Pragmatic sbt
 
Type Classes in Scala and Haskell
Type Classes in Scala and HaskellType Classes in Scala and Haskell
Type Classes in Scala and Haskell
 
Reactive Access to MongoDB from Scala
Reactive Access to MongoDB from ScalaReactive Access to MongoDB from Scala
Reactive Access to MongoDB from Scala
 
Reactive Access to MongoDB from Java 8
Reactive Access to MongoDB from Java 8Reactive Access to MongoDB from Java 8
Reactive Access to MongoDB from Java 8
 
Implementing a many-to-many Relationship with Slick
Implementing a many-to-many Relationship with SlickImplementing a many-to-many Relationship with Slick
Implementing a many-to-many Relationship with Slick
 

Recently uploaded

Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxbodapatigopi8531
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfkalichargn70th171
 
Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...aditisharan08
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providermohitmore19
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptkotipi9215
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataBradBedford3
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number SystemsJheuzeDellosa
 
Introduction to Decentralized Applications (dApps)
Introduction to Decentralized Applications (dApps)Introduction to Decentralized Applications (dApps)
Introduction to Decentralized Applications (dApps)Intelisync
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about usDynamic Netsoft
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 

Recently uploaded (20)

Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptx
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
 
Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
Exploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the ProcessExploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the Process
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.ppt
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number Systems
 
Introduction to Decentralized Applications (dApps)
Introduction to Decentralized Applications (dApps)Introduction to Decentralized Applications (dApps)
Introduction to Decentralized Applications (dApps)
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about us
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 

From Functor Composition to Monad Transformers

  • 1. From Functor Composition to Monad Transformers © 2018 Hermann Hueck https://github.com/hermannhueck/monad-transformers 1 / 91
  • 2. 1 / 91 Abstract In a List[Option[A]] or Future[Option[A]] we want to access the value of type A conveniently without nested mapping and flatMapping. We can avoid nested mapping with composed Functors. Functors compose, but Monads do not! What can we do? Monad transformers to the rescue! After going through Functor composition I show how 2 Monads are bolted together with a Monad transformer and how to use this construct. I demonstrate this with the Option transformer OptionT and will end up with best practices. 2 / 91
  • 3. 2 / 91 Agenda 1. Nested Monads in for comprehensions 2. Functors compose. 3. Aside: Creating List[A => B] and List[Option[A => B]] 4. Applicatives compose. 5. Do Monads compose? 6. Monad Transformers - Example OptionT 7. Generic Monad Processing 8. Stacking Monads and Transformers 9. Making the Types Fit 10. Monad Transformers in Cats 11. Best Practices 12. OptionT - Implementation 13. Resources 3 / 91
  • 4. 3 / 91 1. Nested Monads in for comprehensions 4 / 91
  • 5. 4 / 91 Imports for (all of) Cats import cats._, cats.data._, cats.implicits._ // import mycats._, transform._, Functor.ops._, Monad.ops._ 5 / 91
  • 6. 5 / 91 Imports for my own implementation of Functor, Applicative, Monad and OptionT // import cats._, cats.data._, cats.implicits._ import mycats._, transform._, Functor.ops._, Monad.ops._ The same client code works with Cats and with my implementation. Only the imports need to be changed! 6 / 91
  • 7. 6 / 91 For comprehension for List[Option[Int]] val loi1: List[Option[Int]] = List(Some(1), None, Some(2), Some(3)) val loi2: List[Option[Int]] = List(Some(1), None, Some(2), Some(3)) val result1: List[Option[Int]] = for { oi1: Option[Int] <- loi1 if oi1.isDefined oi2: Option[Int] <- loi2 if oi2.isDefined } yield Option(oi1.get * oi2.get) // result1: List[Option[Int]] = List(Some(1), Some(2), // Some(3), Some(2), Some(4), Some(6), Some(3), Some(6), Some(9)) 7 / 91
  • 8. 7 / 91 Compiler translates for comprehension to flatMap, map (and withFilter) val result2 = loi1 .filter(_.isDefined) .flatMap { oi1 => loi2 .filter(_.isDefined) .map { oi2 => Option(oi1.get * oi2.get) } } // result2: List[Option[Int]] = List(Some(1), Some(2), // Some(3), Some(2), Some(4), Some(6), Some(3), Some(6), Some(9)) 8 / 91
  • 9. 8 / 91 A nested for comprehension val result3: List[Option[Int]] = for { oi1: Option[Int] <- loi1 oi2: Option[Int] <- loi2 } yield for { i1: Int <- oi1 i2: Int <- oi2 } yield i1 * i2 // result3: List[Option[Int]] = List(Some(1), None, // Some(2), Some(3), None, None, None, None, Some(2), // None, Some(4), Some(6), Some(3), None, Some(6), Some(9)) 9 / 91
  • 10. 9 / 91 Wanted: a for comprehension to process Ints nested in 2 contexts ... something like this one: // This code does not compile! val result4: List[Option[Int]] = for { i1: Int <- loi1 i2: Int <- loi2 } yield i1 * i2 ... or like this one: // How can we create such a ListOption? val result4: ListOption[Int] = for { i1: Int <- loi1 i2: Int <- loi2 } yield i1 * i2 10 / 91
  • 11. 10 / 91 To be usable in a for comprehension our "ListOption" must provide "map", "flatMap" (and "withFilter") operations. Hence it must be a Functor (for map) as well a Monad (for flatMap). Every Monad is also a Functor. But lets first look at Functors. (It's the simpler part.) 11 / 91
  • 12. 11 / 91 2. Functors compose. 12 / 91
  • 13. 12 / 91 Recap: What is a Functor? (1/2) // The gist of typeclass Functor trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] } 13 / 91
  • 14. 13 / 91 Recap: What is a Functor? (2/2) // Simplified typeclass Functor trait Functor[F[_]] { // --- intrinsic abstract functions def map[A, B](fa: F[A])(f: A => B): F[B] // --- concrete functions implemented in terms of map def fmap[A, B](fa: F[A])(f: A => B): F[B] = map(fa)(f) // alias for map def compose[G[_]: Functor]: Functor[Lambda[X => F[G[X]]]] = ??? // ... other functions ... } 14 / 91
  • 15. 14 / 91 Mapping a List[Int] with a Functor[List] val li = List(1, 2, 3) val lSquared1 = li.map(x => x * x) // invokes List.map // lSquared1: List[Int] = List(1, 4, 9) val lSquared2 = li.fmap(x => x * x) // invokes Functor[List].fmap // lSquared2: List[Int] = List(1, 4, 9) val lSquared3 = Functor[List].map(li)(x => x * x) // invokes Functor[List].map // lSquared3: List[Int] = List(1, 4, 9) 15 / 91
  • 16. 15 / 91 Mapping a List[Option[Int]] with Functor[List] and Functor[Option] val loi = List(Some(1), None, Some(2), Some(3)) val loSquared1 = loi.map(oi => oi.map(x => x * x)) // loSquared1: List[Option[Int]] = List(Some(1), None, Some(4), Some(9)) val loSquared2 = Functor[List].map(loi)(oi => Functor[Option].map(oi)(x => x * x)) // loSquared2: List[Option[Int]] = List(Some(1), None, Some(4), Some(9)) val loSquared3 = (Functor[List] compose Functor[Option]).map(loi)(x => x * x) // loSquared3: List[Option[Int]] = List(Some(1), None, Some(4), Some(9)) val cf = Functor[List] compose Functor[Option] val loSquared4 = cf.map(loi)(x => x * x) // loSquared4: List[Option[Int]] = List(Some(1), None, Some(4), Some(9)) 16 / 91
  • 17. 16 / 91 Pimping List[Option[A]] implicit class PimpedListOptionA[A](list: List[Option[A]]) { val composedFunctor = Functor[List] compose Functor[Option] def map[B](f: A => B): List[Option[B]] = composedFunctor.map(list)(f) def fmap[B](f: A => B): List[Option[B]] = composedFunctor.map(list)(f) } val loSquared5 = loi.fmap(x => x * x) // loSquared5: List[Option[Int]] = List(Some(1), None, Some(4), Some(9)) // Attention!!! fmap works but map still doesn't work!!! val loSquared6 = loi.map(x => x * x) <console>:25: error: value * is not a member of Option[Int] val loSquared6 = loi.map(x => x * x) val loSquared7 = for { x <- loi } yield x * x <console>:22: error: value * is not a member of Option[Int] val loSquared7 = for { x <- loi } yield x * x 17 / 91
  • 18. 17 / 91 3. Aside: Creating List[A => B] and List[Option[A => B]] 18 / 91
  • 19. 18 / 91 Creating a List[Int => Int] val lf1_1 = List((x:Int) => x * 1, (x:Int) => x * 2, (x:Int) => x * 3) // lf1_1: List[Int => Int] = List($$Lambda$..., $$Lambda$..., $$Lambda$...) val lf1_2 = List((_:Int) * 1, (_:Int) * 2, (_:Int) * 3) // lf1_2: List[Int => Int] = List($$Lambda$..., $$Lambda$..., $$Lambda$...) 19 / 91
  • 20. 19 / 91 Creating a List[Int => Int] from List[Int] val lf1_3 = List(1, 2, 3).map(x => (y:Int) => y * x) // lf1_3: List[Int => Int] = List($$Lambda$..., $$Lambda$..., $$Lambda$...) val lf1_4 = List(1, 2, 3).map(x => (_:Int) * x) // lf1_4: List[Int => Int] = List($$Lambda$..., $$Lambda$..., $$Lambda$...) 20 / 91
  • 21. 20 / 91 Creating a List[Option[Int => Int]] val lf1 = lf1_4 // lf1: List[Int => Int] = List($$Lambda$..., $$Lambda$..., $$Lambda$...) val lof1 = lf1 map Option.apply // lof1: List[Option[Int => Int]] = // List(Some($$Lambda$...), Some($$Lambda$...), Some($$Lambda$...)) 21 / 91
  • 22. 21 / 91 4. Applicatives compose. 22 / 91
  • 23. 22 / 91 Recap: What is an Applicative? (1/2) // The gist of typeclass Applicative trait Applicative[F[_]] extends Functor[F] { def pure[A](a: A): F[A] def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] } 23 / 91
  • 24. 23 / 91 Recap: What is an Applicative? (2/2) // Simplified typeclass Applicative trait Applicative[F[_]] extends Functor[F] { // --- intrinsic abstract Applicative methods def pure[A](a: A): F[A] def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] // --- method implementations in terms of pure and ap def ap2[A, B, Z](ff: F[(A, B) => Z])(fa: F[A], fb: F[B]): F[Z] = ??? override def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa) def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) Z): F[Z] = ??? def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = ??? def tuple2[A, B](fa: F[A], fb: F[B]): F[(A, B)] = product(fa, fb) def compose[G[_]: Applicative]: Applicative[Lambda[X => F[G[X]]]] = ??? // ... other functions ... } 24 / 91
  • 25. Applying a List[Int => Int] to a List[Int] with Applicative val liResult = Applicative[List].ap(lf1)(li) // liResult: List[Int] = List(1, 2, 3, 2, 4, 6, 3, 6, 9) 25 / 91 24 / 91
  • 26. 25 / 91 Applying a List[Option[Int => Int]] to a List[Option[Int]] with Applicative val ca = Applicative[List] compose Applicative[Option] val loiResult = ca.ap(lof1)(loi) // loiResult: List[Option[Int]] = List(Some(1), None, Some(2), Some(3), // Some(2), None, Some(4), Some(6), Some(3), None, Some(6), Some(9)) 26 / 91
  • 27. 26 / 91 5. Do Monads compose? 27 / 91
  • 28. 27 / 91 Recap: What is a Monad? (1/2) // The gist of typeclass Monad trait Monad[F[_]] extends Applicative[F] { def pure[A](a: A): F[A] def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] } 28 / 91
  • 29. 28 / 91 Recap: What is a Monad? (2/2) // Simplified typeclass Monad trait Monad[F[_]] extends Applicative[F] { // --- intrinsic abstract functions def pure[A](a: A): F[A] def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] // --- concrete functions implemented in terms of flatMap and pure override def map[A, B](fa: F[A])(f: A => B): F[B] = flatMap(fa)(a => pure(f(a))) def fmap[A, B](fa: F[A])(f: A => B): F[B] = map(fa)(f) // alias for map def flatten[A](ffa: F[F[A]]): F[A] = flatMap(ffa)(identity) override def ap[A, B](ff: F[A => B])(fa: F[A]): F[B] = ??? // ... other functions ... } 29 / 91
  • 30. 29 / 91 Do Monads compose? val cf = Functor[List] compose Functor[Option] // cf: cats.Functor[[α]List[Option[α]]] = cats.Functor$$anon$1@386ed52a val ca = Applicative[List] compose Applicative[Option] // ca: cats.Applicative[[α]List[Option[α]]] = cats.Alternative$$anon$1@69102316 val cm = Monad[List] compose Monad[Option] // cm: cats.Applicative[[α]List[Option[α]]] = cats.Alternative$$anon$1@48b5e9dd 30 / 91
  • 31. 30 / 91 Trying to compose 2 generic Monads We cannot implement flatMap without knowing the higher kinded type of the inner Monad. // Hypothetical composition def composeFWithGImpossible[F[_]: Monad, G[_]: Monad] = { type Composed[A] = F[G[A]] new Monad[Composed] { def pure[A](a: A): Composed[A] = Monad[F].pure(Monad[G].pure(a)) def flatMap[A, B](fa: Composed[A])(f: A => Composed[B]): Composed[B] = ??? // !!! Problem! How do we write flatMap? Impossible to implement // !!! generically without knowledge of the type of the inner Monad! } } 31 / 91
  • 32. 31 / 91 Trying to compose 1 generic and 1 concrete Monad Knowing the higher kinded type of the inner Monad (Option) we can implement flatMap. def composeFWithOption[F[_]: Monad] = { type Composed[A] = F[Option[A]] new Monad[Composed] { def pure[A](a: A): Composed[A] = Monad[F].pure(Monad[Option].pure(a)) def flatMap[A, B](fOptA: Composed[A])(f: A => Composed[B]): Composed[B] = Monad[F].flatMap(fOptA) { case None => Monad[F].pure(Option.empty[B]) case Some(a) => f(a) } } } 32 / 91
  • 33. 32 / 91 Do Monads compose? Functors compose. Applicatives compose. Monads do NOT compose! 33 / 91
  • 34. 33 / 91 Do Monads compose? Functors compose. Applicatives compose. Monads do NOT compose! Solution: Monad Transformers 34 / 91
  • 35. 34 / 91 Do Monads compose? Functors compose. Applicatives compose. Monads do NOT compose! Solution: Monad Transformers Monad Transformers are some kind of composition mechanism for Monads. 35 / 91
  • 36. 35 / 91 Do Monads compose? Functors compose. Applicatives compose. Monads do NOT compose! Solution: Monad Transformers Monad Transformers are some kind of composition mechanism for Monads. The inner Monad must be concrete, e.g. Option or Either. The outer Monad is left abstract. F[_]: Monad 36 / 91
  • 37. 36 / 91 6. Monad Transformers - Example OptionT 37 / 91
  • 38. 37 / 91 What is OptionT? OptionT is a generic case class which encapsulates an F[Option[A]]. F is a place holder for any Monad: List, Future, Either[A, ?], Id etc. A Monad instance for OptionT (implementing pure and flatMap) must be provided implicitly in order to allow flatMapping over OptionT. final case class OptionT[F[_]: Monad, A](value: F[Option[A]]) { // ... } object OptionT { implicit def monad[F[_]]: Monad[F]: Monad[OptionT[F, ?]] = new Monad[OptionT[F, ?]] { override def pure[A](a: A) = ??? override def flatMap[A, B](fa)(f) = ??? } } 38 / 91
  • 39. 38 / 91 Creating an OptionT[List, Int] to encapsulate a List[Option[Int]] val loi = List(Some(1), None, Some(2), Some(3)) // loi: List[Option[Int]] = List(Some(1), None, Some(2), Some(3)) val otli = OptionT[List, Int](loi) // otli: cats.data.OptionT[List,Int] = OptionT(List(Some(1), None, Some(2), Some(3))) otli.value // same as: loi // res6: List[Option[Int]] = List(Some(1), None, Some(2), Some(3)) 39 / 91
  • 40. 39 / 91 FlatMapping an OptionT[List, Int] flatMap takes a function of type: A => OptionT[F, B] In our case the type is: Int => OptionT[List, String] def fillListWith(x: Int): List[Option[String]] = List.fill(x)(Option(x.toString)) val otliFlatMapped = otli.flatMap(x => OptionT[List, String](fillListWith(x))) // otliFlatMapped: cats.data.OptionT[List,String] = // OptionT(List(Some(1), None, Some(2), Some(2), Some(3), Some(3), Some(3))) otliFlatMapped.value // res7: List[Option[String]] = // List(Some(1), None, Some(2), Some(2), Some(3), Some(3), Some(3)) 40 / 91
  • 41. 40 / 91 Convenience function flatMapF flatMapF takes a function of type: A => F[Option[B]] In our case the type is: Int => List[Option[String]] def fillListWith(x: Int): List[Option[String]] = List.fill(x)(Option(x.toString)) val otliFlatMappedF = otli.flatMapF(x => fillListWith(x)) // otliFlatMappedF: cats.data.OptionT[List,String] = // OptionT(List(Some(1), None, Some(2), Some(2), Some(3), Some(3), Some(3))) otliFlatMappedF.value // res8: List[Option[String]] = // List(Some(1), None, Some(2), Some(2), Some(3), Some(3), Some(3)) 41 / 91
  • 42. 41 / 91 Mapping an OptionT[List, Int] val otliMapped = Monad[OptionT[List, ?]].map(otli) { _.toString + "!" } // otliMapped: cats.data.OptionT[[+A]List[A],String] = // OptionT(List(Some(1!), None, Some(2!), Some(3!))) otliMapped.value // res9: List[Option[String]] = List(Some(1!), None, Some(2!), Some(3!)) 42 / 91
  • 43. 42 / 91 OptionT.{isDefined, isEmpty, getOrElse, fold} otli.isDefined // res10: List[Boolean] = List(true, false, true, true) otli.isEmpty // res11: List[Boolean] = List(false, true, false, false) otli.getOrElse(42) // res12: List[Int] = List(1, 42, 2, 3) otli.fold(42.0)(_.toDouble) // res13: List[Int] = List(1.0, 42.0, 2.0, 3.0) 43 / 91
  • 44. 43 / 91 For comprehension with OptionT[List, Int] encapsulating List[Option[Int]] val result4: OptionT[List, Int] = for { x <- otli y <- otli } yield x * y // result4: cats.data.OptionT[List,Int] = // OptionT(List(Some(1), None, Some(2), Some(3), None, Some(2), // None, Some(4), Some(6), Some(3), None, Some(6), Some(9))) result4.value // res13: List[Option[Int]] = // List(Some(1), None, Some(2), Some(3), None, Some(2), // None, Some(4), Some(6), Some(3), None, Some(6), Some(9)) 44 / 91
  • 45. 44 / 91 7. Generic Monad Processing 45 / 91
  • 46. 45 / 91 Using processIntMonads with OptionT OptionT is a Monad which encapsulates two other Monads. Hence an OptionT can be passed to a function accepting any Monad F[Int]. def processIntMonads[F[_]: Monad](monad1: F[Int], monad2: F[Int]): F[Int] = for { x <- monad1 y <- monad2 } yield x * y val result5 = processIntMonads(otli, otli) // result5: cats.data.OptionT[List,Int] = OptionT(List( // Some(1), None, Some(2), Some(3), None, Some(2), // None, Some(4), Some(6), Some(3), None, Some(6), Some(9))) result5.value // res14: List[Option[Int]] = List( // Some(1), None, Some(2), Some(3), None, Some(2), // None, Some(4), Some(6), Some(3), None, Some(6), Some(9)) 46 / 91
  • 47. 46 / 91 Using processIntMonads with other Monads List Vector Option Future processIntMonads(List(1, 2, 3), List(10, 20, 30)) // res16: List[Int] = List(10, 20, 30, 20, 40, 60, 30, 60, 90) processIntMonads(Vector(1, 2, 3), Vector(10, 20, 30)) // res17: scala.collection.immutable.Vector[Int] = Vector(10, 20, 30, 20, 40, 60, 30, 60, 90) processIntMonads(Option(5), Option(5)) // res18: Option[Int] = Some(25) val fi = processIntMonads(Future(5), Future(5)) // fi: scala.concurrent.Future[Int] = Future(<not completed>) Await.ready(fi, 1.second) fi // res20: scala.concurrent.Future[Int] = Future(Success(25)) 47 / 91
  • 48. 47 / 91 Using processIntMonads with other OptionT- Monads OptionT[Vector, Int] OptionT[Option, Int] OptionT[Future, Int] val otvi = OptionT[Vector, Int](Vector(Option(3), Option(5))) processIntMonads(otvi, otvi).value // res21: Vector[Option[Int]] = Vector(Some(9), Some(15), Some(15), Some(25)) val otoi = OptionT[Option, Int](Option(Option(5))) processIntMonads(otoi, otoi).value // res22: Option[Option[Int]] = Some(Some(25)) val otfi = processIntMonads(OptionT(Future(Option(5))), OptionT(Future(Option(5)))) Await.ready(otfi.value, 1.second) otfi.value // res23: scala.concurrent.Future[Option[Int]] = Future(Success(Some(25))) 48 / 91
  • 49. 48 / 91 8. Stacking Monads and Transformers 49 / 91
  • 50. 49 / 91 Stacking 3 Monads with 2 transformers A database query should be async -> use a Future[???] Accessing the DB may give you an error -> Future[Either[String, ???]] The searched entity may not be there -> Future[Either[String, Option[???]]] With EitherT and OptionT we can stack the 3 Monads together. def compute[A]: A => Future[Either[String, Option[A]]] = input => Future(Right(Some(input))) def stackMonads[A]: A => OptionT[EitherT[Future, String, ?], A] = input => OptionT(EitherT(compute(input))) val stackedResult: OptionT[EitherT[Future, String, ?], Int] = for { a <- stackMonads(10) b <- stackMonads(32) } yield a + b val future: Future[Either[String, Option[Int]]] = stackedResult.value.value val result: Either[String, Option[Int]] = Await.result(future, 3.seconds) println(result.right.get) // 42 50 / 91
  • 51. 50 / 91 9. Making the Types Fit 51 / 91
  • 52. 51 / 91 When types don't fit ... type Nickname = String type Name = String type Age = Int final case class User(name: Name, age: Age, nickname: Nickname) // return types of these functions are incompatible def getUser(nickname: Nickname): Future[Option[User]] = ??? def getAge(user: User): Future[Age] = ??? def getName(user: User): Option[Name] = ??? 52 / 91
  • 53. 52 / 91 ... make them fit ... the classical way def getNameAge(nickname: Nickname): Future[Option[(Name, Age)]] = (for { user <- OptionT(getUser(nickname)) age <- OptionT(getAge(user).map(Option(_))) name <- OptionT(Future(getName(user))) } yield (name, age)).value 53 / 91
  • 54. 53 / 91 ... make them fit ... the classical way def getNameAge(nickname: Nickname): Future[Option[(Name, Age)]] = (for { user <- OptionT(getUser(nickname)) age <- OptionT(getAge(user).map(Option(_))) name <- OptionT(Future(getName(user))) } yield (name, age)).value ... or with OptionT.liftF and OptionT.fromOption. def getNameAge(nickname: Nickname): Future[Option[(Name, Age)]] = (for { user <- OptionT(getUser(nickname)) age <- OptionT.liftF(getAge(user)) name <- OptionT.fromOption(getName(user)) } yield (name, age)).value 54 / 91
  • 55. 54 / 91 Compare the solution without OptionT def getNameAge(nickname: Nickname): Future[Option[(Name, Age)]] = for { maybeUser <- getUser(nickname) if maybeUser.isDefined user = maybeUser.get maybeAge <- getAge(user).map(Option(_)) maybeName <- Future(getName(user)) } yield for { name <- maybeName age <- maybeAge } yield (name, age) 55 / 91
  • 56. 55 / 91 10. Monad Transformers in Cats cats.data.OptionT for Option cats.data.EitherT for Either cats.data.IdT for Id cats.data.WriterT for Writer cats.data.ReaderT for Reader (ReaderT is an alias for Kleisli) cats.data.StateT for State 56 / 91
  • 57. 56 / 91 11. Best Practices 57 / 91
  • 58. 57 / 91 Don't expose Monad transformers to your API! 58 / 91
  • 59. 58 / 91 Don't expose Monad transformers to your API! This would make your API harder to understand. 59 / 91
  • 60. 59 / 91 Don't expose Monad transformers to your API! This would make your API harder to understand. The user of your API may not know what a Monad transformer is. 60 / 91
  • 61. 60 / 91 Don't expose Monad transformers to your API! This would make your API harder to understand. The user of your API may not know what a Monad transformer is. Just call transformer.value before you expose it. 61 / 91
  • 62. 61 / 91 Don't expose Monad transformers to your API! This would make your API harder to understand. The user of your API may not know what a Monad transformer is. Just call transformer.value before you expose it. Thus you expose List[Option[A]] instead of OptionT[List, A]. 62 / 91
  • 63. 62 / 91 Don't expose Monad transformers to your API! This would make your API harder to understand. The user of your API may not know what a Monad transformer is. Just call transformer.value before you expose it. Thus you expose List[Option[A]] instead of OptionT[List, A]. Or you expose Future[Option[A]] instead of OptionT[Future, A]. See example 63 / 91
  • 64. 63 / 91 Don't stack too many Monads! 64 / 91
  • 65. 64 / 91 Don't stack too many Monads! A Monad transformer is just another Monad (wrapping 2 Monads). 65 / 91
  • 66. 65 / 91 Don't stack too many Monads! A Monad transformer is just another Monad (wrapping 2 Monads). You can wrap 3 Monads with 2 transformers (= 5 Monads). 66 / 91
  • 67. 66 / 91 Don't stack too many Monads! A Monad transformer is just another Monad (wrapping 2 Monads). You can wrap 3 Monads with 2 transformers (= 5 Monads). You can wrap 4 Monads with 3 transformers (= 7 Monads), etc. 67 / 91
  • 68. 67 / 91 Don't stack too many Monads! A Monad transformer is just another Monad (wrapping 2 Monads). You can wrap 3 Monads with 2 transformers (= 5 Monads). You can wrap 4 Monads with 3 transformers (= 7 Monads), etc. Too many stacked transformers do not make your code clearer. 68 / 91
  • 69. 68 / 91 Don't stack too many Monads! A Monad transformer is just another Monad (wrapping 2 Monads). You can wrap 3 Monads with 2 transformers (= 5 Monads). You can wrap 4 Monads with 3 transformers (= 7 Monads), etc. Too many stacked transformers do not make your code clearer. Too many stacked transformers may degrade performance. 69 / 91
  • 70. 69 / 91 Don't stack too many Monads! A Monad transformer is just another Monad (wrapping 2 Monads). You can wrap 3 Monads with 2 transformers (= 5 Monads). You can wrap 4 Monads with 3 transformers (= 7 Monads), etc. Too many stacked transformers do not make your code clearer. Too many stacked transformers may degrade performance. Structures consume heap. 70 / 91
  • 71. 70 / 91 Don't stack too many Monads! A Monad transformer is just another Monad (wrapping 2 Monads). You can wrap 3 Monads with 2 transformers (= 5 Monads). You can wrap 4 Monads with 3 transformers (= 7 Monads), etc. Too many stacked transformers do not make your code clearer. Too many stacked transformers may degrade performance. Structures consume heap. Does it really slow down your app? Maybe IO is the bottleneck? Benchmark if necessary! 71 / 91
  • 72. 71 / 91 Don't stack too many Monads! A Monad transformer is just another Monad (wrapping 2 Monads). You can wrap 3 Monads with 2 transformers (= 5 Monads). You can wrap 4 Monads with 3 transformers (= 7 Monads), etc. Too many stacked transformers do not make your code clearer. Too many stacked transformers may degrade performance. Structures consume heap. Does it really slow down your app? Maybe IO is the bottleneck? Benchmark if necessary! Consider other approaches: 72 / 91
  • 73. 72 / 91 Don't stack too many Monads! A Monad transformer is just another Monad (wrapping 2 Monads). You can wrap 3 Monads with 2 transformers (= 5 Monads). You can wrap 4 Monads with 3 transformers (= 7 Monads), etc. Too many stacked transformers do not make your code clearer. Too many stacked transformers may degrade performance. Structures consume heap. Does it really slow down your app? Maybe IO is the bottleneck? Benchmark if necessary! Consider other approaches: Free Monads 73 / 91
  • 74. 73 / 91 Don't stack too many Monads! A Monad transformer is just another Monad (wrapping 2 Monads). You can wrap 3 Monads with 2 transformers (= 5 Monads). You can wrap 4 Monads with 3 transformers (= 7 Monads), etc. Too many stacked transformers do not make your code clearer. Too many stacked transformers may degrade performance. Structures consume heap. Does it really slow down your app? Maybe IO is the bottleneck? Benchmark if necessary! Consider other approaches: Free Monads Tagless Final 74 / 91
  • 75. 74 / 91 Don't stack too many Monads! A Monad transformer is just another Monad (wrapping 2 Monads). You can wrap 3 Monads with 2 transformers (= 5 Monads). You can wrap 4 Monads with 3 transformers (= 7 Monads), etc. Too many stacked transformers do not make your code clearer. Too many stacked transformers may degrade performance. Structures consume heap. Does it really slow down your app? Maybe IO is the bottleneck? Benchmark if necessary! Consider other approaches: Free Monads Tagless Final Or: Go back to nested for comprehensions. 75 / 91
  • 76. 75 / 91 Don't stack too many Monads! A Monad transformer is just another Monad (wrapping 2 Monads). You can wrap 3 Monads with 2 transformers (= 5 Monads). You can wrap 4 Monads with 3 transformers (= 7 Monads), etc. Too many stacked transformers do not make your code clearer. Too many stacked transformers may degrade performance. Structures consume heap. Does it really slow down your app? Maybe IO is the bottleneck? Benchmark if necessary! Consider other approaches: Free Monads Tagless Final Or: Go back to nested for comprehensions. Follow the "Principle of least power"! 76 / 91
  • 77. 76 / 91 12. OptionT - Implementation 77 / 91
  • 78. 77 / 91 ListOption - implementation final case class ListOption[A](value: List[Option[A]]) { def map[B](f: A => B): ListOption[B] = ListOption(value.map(_ map f)) def flatMap[B](f: A => ListOption[B]): ListOption[B] = ListOption( value.flatMap { case None => List(Option.empty[B]) case Some(a) => f(a).value } ) def flatMapF[B](f: A => List[Option[B]]): ListOption[B] = flatMap(a => ListOption(f(a))) def isDefined: List[Boolean] = value.map(_.isDefined) def isEmpty: List[Boolean] = value.map(_.isEmpty) def getOrElse(default: => A): List[A] = value.map(_.getOrElse(default)) } object ListOption { implicit def monad: Monad[ListOption] = new Monad[ListOption] { override def pure[A](a: A): ListOption[A] = ListOption(List(Option(a))) override def flatMap[A, B](fa: ListOption[A]) (f: A => ListOption[B]): ListOption[B] = fa flatMap f } } 78 / 91
  • 79. 78 / 91 Abstracting over List gives you OptionT. 79 / 91
  • 80. 79 / 91 Abstracting over List gives you OptionT. Parameterize ListOption with type constructor: ListOption[F[_], A] 80 / 91
  • 81. 80 / 91 Abstracting over List gives you OptionT. Parameterize ListOption with type constructor: ListOption[F[_], A] F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A] 81 / 91
  • 82. 81 / 91 Abstracting over List gives you OptionT. Parameterize ListOption with type constructor: ListOption[F[_], A] F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A] Save an instance of Monad F in a value: val F = Monad[F] 82 / 91
  • 83. 82 / 91 Abstracting over List gives you OptionT. Parameterize ListOption with type constructor: ListOption[F[_], A] F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A] Save an instance of Monad F in a value: val F = Monad[F] Replace every occurence of List with type constructor F. 83 / 91
  • 84. 83 / 91 Abstracting over List gives you OptionT. Parameterize ListOption with type constructor: ListOption[F[_], A] F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A] Save an instance of Monad F in a value: val F = Monad[F] Replace every occurence of List with type constructor F. Replace every ListOption[A] with ListOption[F, A]. 84 / 91
  • 85. 84 / 91 Abstracting over List gives you OptionT. Parameterize ListOption with type constructor: ListOption[F[_], A] F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A] Save an instance of Monad F in a value: val F = Monad[F] Replace every occurence of List with type constructor F. Replace every ListOption[A] with ListOption[F, A]. Replace invocations of map/flatMap on List by invocations on Monad F. 85 / 91
  • 86. 85 / 91 Abstracting over List gives you OptionT. Parameterize ListOption with type constructor: ListOption[F[_], A] F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A] Save an instance of Monad F in a value: val F = Monad[F] Replace every occurence of List with type constructor F. Replace every ListOption[A] with ListOption[F, A]. Replace invocations of map/flatMap on List by invocations on Monad F. Replace invocations of List.apply by invocations of F.pure. 86 / 91
  • 87. 86 / 91 Abstracting over List gives you OptionT. Parameterize ListOption with type constructor: ListOption[F[_], A] F must be a Monad! Use bounded context: ListOption[F[_]: Monad, A] Save an instance of Monad F in a value: val F = Monad[F] Replace every occurence of List with type constructor F. Replace every ListOption[A] with ListOption[F, A]. Replace invocations of map/flatMap on List by invocations on Monad F. Replace invocations of List.apply by invocations of F.pure. Rename ListOption to OptionT. 87 / 91
  • 88. 87 / 91 OptionT - implementation final case class OptionT[F[_]: Monad, A](value: F[Option[A]]) { val F = Monad[F] def map[B](f: A => B): OptionT[F, B] = OptionT(F.map(value)(_ map f)) def flatMap[B](f: A => OptionT[F, B]): OptionT[F, B] = OptionT( F.flatMap(value) { case None => F.pure(Option.empty[B]) case Some(a) => f(a).value } ) def flatMapF[B](f: A => F[Option[B]]): OptionT[F, B] = flatMap(a => OptionT(f(a))) def isDefined: F[Boolean] = F.map(value)(_.isDefined) def isEmpty: F[Boolean] = F.map(value)(_.isEmpty) def getOrElse(default: => A): F[A] = F.map(value)(_.getOrElse(default)) } object OptionT { implicit def monad[F[_]: Monad]: Monad[OptionT[F, ?]] = new Monad[OptionT[F, ?]] { override def pure[A](a: A): OptionT[F, A] = OptionT(Monad[F].pure(Option(a))) override def flatMap[A, B](fa: OptionT[F, A]) (f: A => OptionT[F, B]): OptionT[F, B] = fa flatMap f } } 88 / 91
  • 89. 88 / 91 13. Resources 89 / 91
  • 90. 89 / 91 Resources Code and Slides of this Talk: https://github.com/hermannhueck/monad-transformers "Scala with Cats" Book by Noel Welsh and Dave Gurnell https://underscore.io/books/scala-with-cats/ "Herding Cats, day 10: Stacking Future and Either" Blogpost by eed3si9n (Eugene Yokota) http://eed3si9n.com/herding-cats/stacking-future-and-either.html "Monad transformers down to earth" Talk by Gabriele Petronella at Scala Days Copenhagen 2017 https://www.youtube.com/watch?v=jd5e71nFEZM "FSiS Part 7 - OptionT transformer" Live coding video tutorial by Michael Pilquist, 2015 https://www.youtube.com/watch?v=ZNUTMabdgzo 90 / 91
  • 91. 90 / 91 Thanks for Listening Q & A https://github.com/hermannhueck/monad-transformers 91 / 91