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
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
Atomic Design, Enforced: Scaling Mobile Design Systems (next.app devCon / droidCon Berlin 2026)
steliosf
PRO
0
170
PumaからFalconに移行したRailsアプリケーションでの実践
falcon8823
0
190
[2026-10-14](C)Re:ゼロから始める異世界生活〜始まりの終わりと終わりの始まり〜
tosite
0
140
當我們開始不讀 Code - AI Agent 時代,Code Review 與 Clean Code 還剩下什麼?
jame2408
1
280
Seven New Principles of Software Engineering
yegor256
0
140
Agentic Software Factoryに、すごく賢いIF文を。 / super smart IF statement into the Agentic Software Factory.
rkaga
8
2.3k
[2026-10-09]一枚の布で作らなくてもいい。 信頼性は「パッチワーク」で紡がれる~既存の運用と新しい可能性をつなぎ合わせてきたCREの話~
tosite
0
110
残高管理から台帳サービスへの進化
artoy
1
210
Kotlinの15年とServer-Side Kotlin
n_takehata
1
160
パスワード再設定メールのURLについて知っておこう
uyuki234
0
170
contenteditable と日本語入力に向き合う
colorful12
0
210
コーディング自動化の次へ。人の介入をゼロにするファクトリーとサポートAIによる実践
timakin
5
4.2k
Featured
See All Featured
KATA
mclloyd
PRO
35
16k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
370
Building a A Zero-Code AI SEO Workflow
portentint
PRO
0
790
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
2
1.6k
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
610
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
2
6.3k
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
2
410
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
Amusing Abliteration
ianozsvald
1
350
Measuring & Analyzing Core Web Vitals
bluesmoon
9
1k
How Software Deployment tools have changed in the past 20 years
geshan
2
35k
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