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
180
Design System Adventures in React
gabro
1
150
Casting Metals
gabro
0
420
Functional Programming in front-end applications
gabro
1
260
Functional Programming in Front-end Applications
gabro
3
250
How to get away with Functional Programming in front-end applications
gabro
3
1.6k
Bridging the tooling gap with Scala.js
gabro
0
330
Monad Transformers Down to Earth
gabro
2
2.9k
Move fast and fix things
gabro
0
410
Other Decks in Programming
See All in Programming
AI時代のFlutter品質設計 — アプリ品質を守るQuality Layers
omatty198
0
330
月間100万人のサービス刷新で、 技術選定をHonoに振り切った話
yutobara
8
7.1k
エージェントを使い続けるためのメタワーク / Meta-work for keeping using the agent
rkaga
6
2.1k
3回つくってわかった、 定期実行ジョブ基盤の本質情報
takai
4
3.5k
AI Augmentation Techniques with Liberty
ivargrimstad
0
250
クラウドエージェントは、 何がそんなに便利なのか / what makes cloud agents so useful
rkaga
5
670
動画プレイヤーはなぜカクつくのか 〜Flutterのレンダリングとネイティブの境界を解剖する〜
naruogram
0
320
當我們開始不讀 Code - AI Agent 時代,Code Review 與 Clean Code 還剩下什麼?
jame2408
1
350
TanStack Vue Query と Pinia Colada から学ぶ非同期状態の型設計
aoshi
0
580
React Native에 현대적인 빌드 도구 더하기: Rollipop 구현기
leegeunhyeok
0
180
SODA - FACT BOOK(JP)
sodainc
1
40k
個人開発者よ、Honoを使え(おじさんver)
tanahiro2010
2
300
Featured
See All Featured
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.6k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
540
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
570
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
350
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.9k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.6k
Facilitating Awesome Meetings
lara
57
7.2k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
68
60k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3.1k
A better future with KSS
kneath
240
18k
Typedesign – Prime Four
hannesfritz
42
3.2k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
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