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
160
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
選挙速報を多くのユーザーへ 届ける Live Activities 設計
hamayokokuririn
0
190
[ハンズオン]AIへの指示だけで「五目並べ」を作ってみよう
satoshi256kbyte
1
310
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
4
430
setup-vp GitLab対応の裏側
naokihaba
0
140
Apple Intelligence を用いた個人情報誤送信防止、及びユーザーリクエスト体験の改善について
yukiny
0
260
20260914 AIエージェント時代のPlatform Engineering LLM基盤とプロダクトの責務境界線
kanfab1
7
2.3k
mrbgem 三角測量 開発
ogom
0
150
Heart of Swift Concurrency
koher
0
1.1k
GitHubハンズオン講座 — 実務レベルのチーム開発のフローを身につけよう
junhat6
0
110
モジュールの視点からSwiftを読み解く #iosdc
s_shimotori
0
280
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
370
AgentCore CLI で進化した AWS での AI エージェントの作り方 : 必要な機能を必要な時に
icoxfog417
PRO
4
400
Featured
See All Featured
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
380
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
910
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
380
The Curious Case for Waylosing
cassininazir
1
550
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
69
66k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
46k
The Language of Interfaces
destraynor
162
27k
A better future with KSS
kneath
240
18k
Making the Leap to Tech Lead
cromwellryan
135
10k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6.1k
Done Done
chrislema
187
17k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
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