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
140
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
難しいけど、読めた。- OSSの入口に立った話。
sts11142
0
110
Vibes Containers 〜AIで変わるコンテナ設計と運用〜
tkikuc
3
550
新卒PdEのリアル
ryu1013
1
510
速習iPhone Duo対応
yuukiw00w
1
710
Webの地図
yosuke_furukawa
PRO
6
4.7k
TiDB Cloudのカスタムコントローラーによるオートスケール対応
takaidohigasi
0
130
UPDATE をやめる — EF Core でマスタをバージョン管理する
panda728
PRO
0
310
JRuby: Past, Present, and Future
headius
0
170
個人開発基盤をまるごとCloudflareに引っ越して爆速で総合的体験を向上させた話
tinykitten
0
200
技術的負債を組織課題として解く-増えすぎたマイクロサービスとの戦い-
reimaru
1
2.2k
Streamlitで実現する自然言語データアプリ開発
ayumu_yamaguchi
1
300
スマートフォンでモールス信号を送受信する 〜スマートフォンのLEDとカメラで作る光通信の設計と実装〜
atsuki_seo
0
160
Featured
See All Featured
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
290
Designing for Performance
lara
611
70k
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
470
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.6k
Ruling the World: When Life Gets Gamed
codingconduct
0
340
4 Signs Your Business is Dying
shpigford
187
23k
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
460
How to make the Groovebox
asonas
2
2.4k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
Music & Morning Musume
bryan
48
7.4k
RailsConf 2023
tenderlove
30
1.6k
It's Worth the Effort
3n
188
29k
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