Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Monad Transformers Down to Earth .- Scalar 2017...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Gabriele Petronella
April 07, 2017
Programming
1.5k
6
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Monad Transformers Down to Earth .- Scalar 2017 - Warsaw
Gabriele Petronella
April 07, 2017
More Decks by Gabriele Petronella
See All by Gabriele Petronella
Design System Adventures in React - ReactJS Day 2024
gabro
0
170
Design System Adventures in React
gabro
1
150
Casting Metals
gabro
0
410
Functional Programming in front-end applications
gabro
1
260
Functional Programming in Front-end Applications
gabro
3
230
How to get away with Functional Programming in front-end applications
gabro
3
1.6k
Bridging the tooling gap with Scala.js
gabro
0
320
Monad Transformers Down to Earth
gabro
2
2.9k
Move fast and fix things
gabro
0
400
Other Decks in Programming
See All in Programming
自サークルのIdPからログアウトできるようになるまで
taiseiue
1
130
コードで差別化できない時代の ソフトウェアエンジニアリング / Software Engineering in an Era Where Code Doesn’t Differentiate
kishida
2
140
すこし踏み込む CancellationToken
htkym
2
1.7k
速習iPhone Duo対応
yuukiw00w
2
970
IBM Bob でAI駆動開発『QC-Playbook』
ichirokano2
0
100
WebRTC映像をAirPlayに対応させる挑戦.pdf
monolithic_adam
0
350
残高管理から台帳サービスへの進化
artoy
0
180
複雑なUIコンポーネントとの ⻑い付き合い⽅:波及するバグからテスト戦略を経て、安全な再設計へ
mixplace
0
290
モデルのリファクタリングが難しいと思ったら、そもそも複雑だったのはビジネス仕様だった ? / is-the-business-domain-the-real-complexity
hatsu38
0
450
KiroのSpecで「五目並べ」を作ってみる
satoshi256kbyte
1
360
Agentic Software Factoryに、すごく賢いIF文を。 / super smart IF statement into the Agentic Software Factory.
rkaga
8
2.1k
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Rails World 2026, Austin, Texas
marcoroth
0
200
Featured
See All Featured
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
560
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
530
Balancing Empowerment & Direction
lara
6
1.3k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
3.2k
Marketing to machines
jonoalderson
1
5.9k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
520
Become a Pro
speakerdeck
PRO
31
6.3k
Making Projects Easy
brettharned
120
6.8k
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
980
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
300
Optimizing for Happiness
mojombo
378
71k
Transcript
GABRIELE PETRONELLA MONAD TRANSFORMERS DOWN TO EARTH
ME, HI! @gabro27 Scalar 2017 - Warsaw
STUFF I DO @gabro27 Scalar 2017 - Warsaw
THIS TALK: WHAT AND WHY @gabro27 Scalar 2017 - Warsaw
THIS TALK: WHAT AND WHY What: The talk I wished
I attended before banging my head against this @gabro27 Scalar 2017 - Warsaw
THIS TALK: WHAT AND WHY What: The talk I wished
I attended before banging my head against this Why: Because I still remember how it was before knowing it @gabro27 Scalar 2017 - Warsaw
A QUESTION @gabro27 Scalar 2017 - Warsaw
@gabro27 Scalar 2017 - Warsaw
THE PROBLEM val x: Future[List[Int]] = ??? futureList.map(list => list.map(f))
^ ^ |________________| 2 maps 1 function @gabro27 Scalar 2017 - Warsaw
CAN WE DO BETTER? @gabro27 Scalar 2017 - Warsaw
INDENT! futureList.map { list => list.map(f) } @gabro27 Scalar 2017
- Warsaw
None
future.map(f) list.map(f) | | | | Functor[Future].map(future)(f) Functor[List].map(list)(f) @gabro27 Scalar
2017 - Warsaw
futureList.map(f) // not really valid scala | // but you
get the point | Functor[Future[List]].map(futureList)(f) @gabro27 Scalar 2017 - Warsaw
IN PRACTICE import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global import cats._; import std.future._;
import std.list._ // create a `Functor[Future[List]` val futureListF = Functor[Future].compose(Functor[List]) val data: Future[List[Int]] = Future(List(1, 2, 3)) // only one map! futureListF.map(data)(_ + 1) // Future(List(2, 3, 4)) @gabro27 Scalar 2017 - Warsaw
CATS https://github.com/typelevel/cats @gabro27 Scalar 2017 - Warsaw
ABOUT FLATTENING List(1, 2, 3).map(_ + 1) // List(2, 3,
4) List(1, 2, 3).map(n => List.fill(n)(n)) // List(List(1), List(2, 2), List(3, 3, 3)) List(1, 2, 3).map(n => List.fill(n)(n)).flatten // List(1, 2, 2, 3, 3, 3) @gabro27 Scalar 2017 - Warsaw
FLATMAP flatten ∘ map = flatMap so List(1, 2, 3).map(n
=> List.fill(n)(n)).flatten == List(1, 2, 3).flatMap(n => List.fill(n)(n)) @gabro27 Scalar 2017 - Warsaw
IN OTHER WORDS when life gives you F[F[A]] you probably
wanted flatMap e.g. val f: Future[Future[Int]] = Future(42).map(x => Future(24)) val g: Future[Int] = Future(42).flatMap(x => Future(24)) @gabro27 Scalar 2017 - Warsaw
A LESS CONTRIVED EXAMPLE def getUser(name: String): Future[User] def getAddress(user:
User): Future[Address] val getCity: Future[String] = getUser("Gabriele").flatMap( gab => getAddress(gab).map( address => address.city ) ) @gabro27 Scalar 2017 - Warsaw
LET'S COMPREHEND THIS val getCity: Future[String] = for { gab
<- getUser("Gabriele") address <- getAddress(gab) } yield address.city @gabro27 Scalar 2017 - Warsaw
LESSONS monads allow sequential execution monads can squash F[F[A]] into
F[A] @gabro27 Scalar 2017 - Warsaw
QUESTIONS? @gabro27 Scalar 2017 - Warsaw
WAIT A SECOND... @gabro27 Scalar 2017 - Warsaw
WHAT ABOUT F[G[X]] @gabro27 Scalar 2017 - Warsaw
BACK TO THE REAL WORLD def getUser(name: String): Future[User] //
<- really? def getAddress(user: User): Future[Address] @gabro27 Scalar 2017 - Warsaw
BACK TO THE REAL WORLD def getUser(name: String): Future[Option[User]] //
better def getAddress(user: User): Future[Option[Address]] @gabro27 Scalar 2017 - Warsaw
UH, OH... val city: Future[Option[String]] = for { gab <-
getUser("Gabriele") address <- getAddress(gab) // ! FAIL } yield address.city @gabro27 Scalar 2017 - Warsaw
EVENTUALLY val city: Future[Option[String]] = for { gab <- getUser("Gabriele")
address <- getAddress(gab.get) // ! } yield address.get.city // ! @gabro27 Scalar 2017 - Warsaw
None
DO YOU EVEN YIELD, BRO? val city: Future[Option[String]] = for
{ maybeGab <- getUser("Gabriele") } yield for { gab <- maybeGab } yield for { maybeAddress <- getAddress(gab) } yield for { address <- maybeAddress } yield address.city @gabro27 Scalar 2017 - Warsaw
futureUser.flatMap(f) maybeUser.flatMap(f) | | | | Monad[Future].flatMap(futureUser)(f) Monad[Option].flatMap(maybeUser)f) @gabro27 Scalar
2017 - Warsaw
futureMaybeUser.flatMap(f) | | Monad[Future[Option]].flatMap(f) @gabro27 Scalar 2017 - Warsaw
futureMaybeUser.flatMap(f) | | Monad[Future[Option]].flatMap(f) @gabro27 Scalar 2017 - Warsaw
MONADS DO NOT COMPOSE HTTP://BLOG.TMORRIS.NET/POSTS/ MONADS-DO-NOT-COMPOSE/ @gabro27 Scalar 2017 -
Warsaw
None
WHAT'S THE IMPOSSIBLE PART? // trivial def compose[F[_]: Functor, G[_]:
Functor]: Functor[F[G[_]]] = ✅ // impossible def compose[M[_]: Monad, N[_]: Monad]: Monad[M[N[_]]] = " // (not valid scala, but you get the idea) @gabro27 Scalar 2017 - Warsaw
MONADS DO NOT COMPOSE GENERICALLY @gabro27 Scalar 2017 - Warsaw
BUT YOU CAN COMPOSE THEM SPECIFICALLY @gabro27 Scalar 2017 -
Warsaw
case class FutOpt[A](value: Future[Option[A]) @gabro27 Scalar 2017 - Warsaw
new Monad[FutOpt] { def pure[A](a: => A): FutOpt[A] = FutOpt(a.pure[Option].pure[Future])
def map[A, B](fa: FutOpt[A])(f: A => B): FutOpt[B] = FutOpt(fa.value.map(optA => optA.map(f))) def flatMap[A, B](fa: FutOpt[A])(f: A => FutOpt[B]): FutOpt[B] = FutOpt(fa.value.flatMap(opt => opt match { case Some(a) => f(a).value case None => (None: Option[B]).pure[Future] })) } @gabro27 Scalar 2017 - Warsaw
AND USE val f: FutOpt[String] = for { gab <-
FutOpt(getUser("Gabriele")) address <- FutOpt(getAddress(gab)) } yield address.city // ! val city: Future[Option[String]] = f.value @gabro27 Scalar 2017 - Warsaw
WHAT IF def getUsers(query: String): List[Option[User]] @gabro27 Scalar 2017 -
Warsaw
case class ListOpt[A](value: List[Option[A]) @gabro27 Scalar 2017 - Warsaw
new Monad[ListOpt] { def pure[A](a: => A): ListOpt[A] = ListOpt(a.pure[Option].pure[List])
def map[A, B](fa: ListOpt[A])(f: A => B): ListOpt[B] = ListOpt(fa.value.map(optA => optA.map(f))) def flatMap[A, B](fa: ListOpt[A])(f: A => ListOpt[B]): ListOpt[B] = ListOpt(fa.value.flatMap(opt => opt match { case Some(a) => f(a).value case None => (None: Option[B]).pure[List] })) } @gabro27 Scalar 2017 - Warsaw
new Monad[FutOpt] { def pure[A](a: => A): FutOpt[A] = FutOpt(a.pure[Option].pure[Future])
def map[A, B](fa: FutOpt[A])(f: A => B): FutOpt[B] = FutOpt(fa.value.map(optA => optA.map(f))) def flatMap[A, B](fa: FutOpt[A])(f: A => FutOpt[B]): FutOpt[B] = FutOpt(fa.value.flatMap(opt => opt match { case Some(a) => f(a).value case None => (None: Option[B]).pure[Future] })) } @gabro27 Scalar 2017 - Warsaw
MEET OptionT OptionT[F[_], A] ^ |___ any monad @gabro27 Scalar
2017 - Warsaw
MEET OptionT val f: OptionT[Future, String] = for { gab
<- OptionT(getUser("Gabriele")) address <- OptionT(getAddress(gab)) } yield address.city // ! val city: Future[Option[String]] = f.value @gabro27 Scalar 2017 - Warsaw
IN GENERAL Foo[Bar[X]] becomes BarT[Foo, X] @gabro27 Scalar 2017 -
Warsaw
ANOTHER EXAMPLE def getUser(id: String): Future[Option[User]] = ??? def getAge(user:
User): Future[Int] = ??? def getNickname(user: User): Option[String] = ??? val lameNickname: Future[Option[String]] = ??? // e.g. Success(Some("gabro27")) @gabro27 Scalar 2017 - Warsaw
I KNOW THE TRICK! val lameNickname: OptionT[Future, String]] = for
{ user <- OptionT(getUser("123")) age <- OptionT(getAge(user)) // sorry, nope name <- OptionT(getName(user)) // sorry, neither } yield s"$name$age" @gabro27 Scalar 2017 - Warsaw
None
DO YOU EVEN LIFT, BRO? val lameNickname: OptionT[Future, String]] =
for { user <- OptionT(getUser("123")) age <- OptionT.liftF(getAge(user)) name <- OptionT.fromOption(getName(user)) } yield s"$name$age" @gabro27 Scalar 2017 - Warsaw
EXAMPLE: UPDATING A USER > check user exists > check
it can be updated > update it @gabro27 Scalar 2017 - Warsaw
THE NAIVE WAY def checkUserExists(id: String): Future[Option[User]] def checkCanBeUpdated(u: User):
Future[Boolean] def updateUserOnDb(u: User): Future[User] @gabro27 Scalar 2017 - Warsaw
PROBLEMS def updateUser(u: User): Future[Option[User]] = checkUserExists.flatMap { maybeUser =>
maybeUser match { case Some(user) => checkCanBeUpdated(user).flatMap { canBeUpdated => if (canBeUpdated) { updateUserOnDb(u) } else { Future(None) } } case None => Future(None) } } @gabro27 Scalar 2017 - Warsaw
DETAILED ERRORS from Option[User] to Either[MyError, User] @gabro27 Scalar 2017
- Warsaw
MORE PROBLEMS (DETAILED ERRORS) case class MyError(msg: String) def updateUser(user:
User): Future[Either[MyError, User]] = checkUserExists(user.id).flatMap { maybeUser => maybeUser match { case Some(user) => checkCanBeUpdated(user).flatMap { canBeUpdated => if (canBeUpdated) { updateUserOnDb(u).map(_.right) } else { Future(MyError("user cannot be updated").left) } } case None => Future(MyError("user not existing").left) } } } @gabro27 Scalar 2017 - Warsaw
! F[Either[A, B]] @gabro27 Scalar 2017 - Warsaw
! EitherT[F[_], A, B] @gabro27 Scalar 2017 - Warsaw
HOW ABOUT case class MyError(msg: String) type ResultT[F[_], A] =
EitherT[F, MyError, A]] type FutureResult[A] = ResultT[Future, A] @gabro27 Scalar 2017 - Warsaw
BETTER? def checkUserExists(id: String): FutureResult[User] = Future { if (id
=== "123") User("123").right else MyError("sorry, no user").left } def checkCanBeUpdated(u: User): FutureResult[User] = ??? def updateUserOnDb(u: User): FutureResult[User] = ??? @gabro27 Scalar 2017 - Warsaw
BETTER? def updateUser(user: User): FutureResult[User] = for { u <-
checkUserExists(user.id) _ <- checkCanBeUpdated(u) updatedUser <- updateUser(user) } yield updatedUser @gabro27 Scalar 2017 - Warsaw
PERSONAL TIPS @gabro27 Scalar 2017 - Warsaw
TIP #1 stacking more than two monads gets bad really
quickly @gabro27 Scalar 2017 - Warsaw
EXAMPLE (FROM DJSPIEWAK/EMM) val effect: OptionT[EitherT[Task, String, ?], String] =
for { first <- readName.liftM[EitherT[?[_], String, ?]].liftM[OptionT] last <- readName.liftM[(EitherT[?[_], String, ?]].liftM[OptionT] name <- if ((first.length * last.length) < 20) OptionT.some[EitherT[Task, String, ?], String](s"$first $last") else OptionT.none[EitherT[Task, String, ?], String] _ <- (if (name == "Daniel Spiewak") EitherT.fromDisjunction[Task](\/.left[String, Unit]("your kind isn't welcome here")) else EitherT.fromDisjunction[Task](\/.right[String, Unit](()))).liftM[OptionT] _ <- log(s"successfully read in $name").liftM[EitherT[?[_], String, ?]].liftM[OptionT] } yield name @gabro27 Scalar 2017 - Warsaw
TIP #2 keep your transformers for youself def publicApiMethod(x: String):
OptionT[Future, Int] = ! def publicApiMethod(x: String): Future[Option[Int]] = " by the way val x: OptionT[Future, Int] = OptionT(Future(Option(42))) val y: Future[Option[Int]] = x.value // Future(Option(42)) @gabro27 Scalar 2017 - Warsaw
TIP #3_ ! Perf! Wrapping/unwrapping isn't cheap, so if you're
concerned about performance, consider benchmarking your code. @gabro27 Scalar 2017 - Warsaw
MONAD TRANSFORMERS: TAKEAWAYS > they end with T > F[G[X]]
becomes GT[F[_], X] > can be stacked undefinitely, but gets awkward > they are a tool for stacking effects @gabro27 Scalar 2017 - Warsaw
WHAT ELSE? @gabro27 Scalar 2017 - Warsaw
FREE MONADS > clearly separate structure and interpretation > effects
are separated from program definition https://github.com/typelevel/cats/blob/master/docs/ src/main/tut/freemonad.md @gabro27 Scalar 2017 - Warsaw
EFF https://github.com/atnos-org/eff-cats "Extensible effects are an alternative to monad transformers
for computing with effects in a functional way" based on Freer Monads, More Extensible Effects by Oleg Kiselyov @gabro27 Scalar 2017 - Warsaw
None
questionsT @gabro27 @buildoHQ @scalaitaly @gabro27 Scalar 2017 - Warsaw