3. • Monad transformers allow different monads to
compose
• Combine effects of monads to create a
SUPER MONAD
• Eg. Future[Option], Future[Either],
Reader[Option]
• In this example, we will use the Cats library...
What are Monad transformers?
5. import scala.concurrent.Future
import cats.data.OptionT
import cats.implicits._
import
scala.concurrent.ExecutionContext.Implicits.glo
bal
case class Beans(fresh: Boolean = true)
case class Grounds()
class GroundBeansException(s: String) extends
Exception(s: String)
1.
Example: Making coffee!
Step 1. Grind the beans
6. def grindFreshBeans(beans: Beans, clumsy: Boolean =
false): Future[Option[Grounds]] = {
if (clumsy) {
Future.failed(new GroundBeansException("We are bad
at grinding"))
} else if (beans.fresh) {
Future.successful(Option(Grounds()))
} else {
Future.successful(None)
}
}
1.
Example: Making coffee!
Step 1. Grind the beans
7. Step 1. Grind the beans
Three different kind of results:
• Value found
• Value not found
• Future failed
Future 3
Example: Making coffee!
8. Step 2. Boil hot water
case class Kettle(filled: Boolean = true)
case class Water()
case class Coffee(delicious: Boolean)
class HotWaterException(s: String) extends
Exception(s: String)
2.
def getHotWater(kettle: Kettle, clumsy: Boolean =
false): Future[Option[Water]] = {
if (clumsy) {
Future.failed(new HotWaterException("Ouch
spilled that water!"))
} else if (kettle.filled) {
Future.successful(Option(Water()))
} else {
Future.successful(None)
}
}
9. Step 3. Combine water and coffee (it's a pourover)
3. ( )
def makingCoffee(grounds: Grounds, water: Water):
Future[Coffee] = {
println(s"Making coffee with... $grounds and
$water")
Future.successful(Coffee(delicious=true))
}
10. val coffeeFut = for {
} yield Option(result)
coffeeFut.onSuccess {
case Some(s) => println(s"SUCCESS: $s")
case None => println("No coffee found?")
}
coffeeFut.onFailure {
case x => println(s"FAIL: $x")
}
Without Monad transformers, success scenario
beans <- grindFreshBeans(Beans(fresh=true))
hotWater <- getHotWater(Kettle(filled=true))
beansResult = beans.getOrElse(throw new Exception("Beans result
errored. "))
waterResult = hotWater.getOrElse(throw new Exception("Water
result errored. "))
result <- makingCoffee(beansResult, waterResult)
11. Without Monad transformers, success scenario
coffeeFut:
scala.concurrent.Future[Option[Coffee]] =
scala.concurrent.impl.Promise
$DefaultPromise@7404ac2
scala> Making coffee with... Grounds() and
Water()
SUCCESS: Coffee(true)
12. With Monad transformers, success scenario
val coffeeFutMonadT = for {
beans <- OptionT(grindFreshBeans(Beans(fresh=true)))
hotWater <- OptionT(getHotWater(Kettle(filled=true)))
result <- OptionT.liftF(makingCoffee(beans, hotWater))
} yield result
coffeeFutMonadT.value.onSuccess {
case Some(s) => println(s"SUCCESS: $s")
case None => println("No coffee found?")
}
coffeeFutMonadT.value.onFailure {
case x => println(s"FAIL: $x")
}
14. OptionT
`fromOption` gives you an OptionT from Option
Internally, it is wrapping your option in a Future.successful()
`liftF` gives you an OptionT from Future
Internally, it is mapping on your Future and wrapping it in a
Some()
Helper functions on OptionT
15. val coffeeFut = for {
beans <- grindFreshBeans(Beans(fresh=false))
hotWater <- getHotWater(Kettle(filled=true))
beansResult = beans.getOrElse(throw new Exception("Beans result
errored. "))
waterResult = hotWater.getOrElse(throw new Exception("Water result
errored. "))
result <- makingCoffee(beansResult, waterResult)
} yield Option(result)
coffeeFut.onSuccess {
case Some(s) => println(s"SUCCESS: $s")
case None => println("No coffee found?")
}
coffeeFut.onFailure {
case x => println(s"FAIL: $x")
}
Without Monad transformers, failure scenario
16. Without Monad transformers, failure scenario
coffeeFut:
scala.concurrent.Future[Option[Coffee]] =
scala.concurrent.impl.Promise
$DefaultPromise@17ee3bd8
scala> FAIL: java.lang.Exception: Beans
result errored.
17. val coffeeFutT = for {
beans <- OptionT(grindFreshBeans(Beans(fresh=false)))
hotWater <- OptionT(getHotWater(Kettle(filled=true)))
result <- OptionT.liftF(makingCoffee(beans,
hotWater))
} yield result
coffeeFutT.value.onSuccess {
case Some(s) => println(s"SUCCESS: $s")
case None => println("No coffee found?")
}
coffeeFutT.value.onFailure {
case x => println(s"FAIL: $x")
}
With Monad transformers, failure scenario
18. With Monad transformers, failure scenario
coffeeFutT:
cats.data.OptionT[scala.concurrent.Future
,Coffee] =
OptionT(scala.concurrent.impl.Promise
$DefaultPromise@4e115bbc)
scala> No coffee found?
19. val coffeeFutT = for {
beans <- OptionT(grindFreshBeans(Beans(fresh=true)))
hotWater <- OptionT(getHotWater(Kettle(filled=true),
clumsy=true))
result <- OptionT.liftF(makingCoffee(beans,
hotWater))
} yield s"$result"
coffeeFutT.value.onSuccess {
case Some(s) => println(s"SUCCESS: $s")
case None => println("No coffee found?")
}
coffeeFutT.value.onFailure {
case x => println(s"FAIL: $x")
}
With monad transformers, failure scenario with
exception
21. flatMap
• Use monad transformers to short circuit your monads
What did we learn?
• Instead of unwrapping layers of monads, monad
transformers results in a new monad to flatMap with
• Reduce layers of x.map( y => y.map ( ... )) to just
x.map ( y => ...))
x.map ( y => y.map ( ... ) ) map
22. OptionT
What’s next?
• Many other types of monad transformers: ReaderT,
WriterT, EitherT, StateT
• Since monad transformers give you a monad as a
result-- you can stack them too!