reducing boilerplate and combining effects: a monad transformer example
TRANSCRIPT
![Page 1: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/1.jpg)
Reducing Boilerplate and Combining Effects:
A Monad Transformer ExampleScala Matsuri - Feb 25th, 2017
Connie Chen
:
![Page 3: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/3.jpg)
• 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?
![Page 4: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/4.jpg)
Future[Either[A, B]] turns into EitherT[Future, A, B]
Future[Option[A]] turns into OptionT[Future, A]
![Page 5: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/5.jpg)
import scala.concurrent.Future import cats.data.OptionT import cats.implicits._ import scala.concurrent.ExecutionContext.Implicits.global
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
![Page 6: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/6.jpg)
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
![Page 7: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/7.jpg)
Step 1. Grind the beans
Three different kind of results: • Value found • Value not found • Future failed
Future 3
Example: Making coffee!
![Page 8: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/8.jpg)
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) } }
![Page 9: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/9.jpg)
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)) }
![Page 10: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/10.jpg)
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)
![Page 11: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/11.jpg)
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)
![Page 12: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/12.jpg)
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") }
![Page 13: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/13.jpg)
coffeeFutMonadT: cats.data.OptionT[scala.concurrent.Future,Coffee] = OptionT(scala.concurrent.impl.Promise$DefaultPromise@4a1c4b40)
scala> Making coffee with... Grounds() and Water() SUCCESS: Coffee(true)
With Monad transformers, success scenario
![Page 14: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/14.jpg)
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
![Page 15: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/15.jpg)
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
![Page 16: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/16.jpg)
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.
![Page 17: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/17.jpg)
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
![Page 18: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/18.jpg)
With Monad transformers, failure scenario
coffeeFutT: cats.data.OptionT[scala.concurrent.Future,Coffee] = OptionT(scala.concurrent.impl.Promise$DefaultPromise@4e115bbc)
scala> No coffee found?
![Page 19: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/19.jpg)
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
![Page 20: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/20.jpg)
FAIL: $line86.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$HotWaterException: Ouch spilled that water!
coffeeFutT: cats.data.OptionT[scala.concurrent.Future,Coffee] = OptionT(scala.concurrent.impl.Promise$DefaultPromise@20e4013)
With monad transformers, failure scenario with exception
![Page 21: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/21.jpg)
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
![Page 22: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/22.jpg)
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!
![Page 23: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/23.jpg)
Thank you
Connie Chen - @coni Twilio
We’re hiring!
![Page 24: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/24.jpg)
final case class OptionT[F[_], A](value: F[Option[A]]) {
def fold[B](default: => B)(f: A => B)(implicit F: Functor[F]): F[B] = F.map(value)(_.fold(default)(f))
def map[B](f: A => B)(implicit F: Functor[F]): OptionT[F, B] = OptionT(F.map(value)(_.map(f)))
def flatMapF[B](f: A => F[Option[B]])(implicit F: Monad[F]): OptionT[F, B] = OptionT(F.flatMap(value)(_.fold(F.pure[Option[B]](None))(f)))
OptionT implementation
![Page 25: Reducing Boilerplate and Combining Effects: A Monad Transformer Example](https://reader034.vdocuments.net/reader034/viewer/2022052117/58ecdb6f1a28abdc698b477b/html5/thumbnails/25.jpg)
def liftF[F[_], A](fa: F[A])(implicit F: Functor[F]): OptionT[F, A] = OptionT(F.map(fa)(Some(_)))
OptionT implementation