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 - Scala Swarm ...
Search
Gabriele Petronella
June 23, 2017
Programming
190
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Monad Transformers down to earth - Scala Swarm 2017 - Porto
Gabriele Petronella
June 23, 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
[2026-10-09]一枚の布で作らなくてもいい。 信頼性は「パッチワーク」で紡がれる~既存の運用と新しい可能性をつなぎ合わせてきたCREの話~
tosite
0
110
3回つくってわかった、 定期実行ジョブ基盤の本質情報
takai
0
590
AIに “答え“を書かせるのを やめてみた
___akari11
0
120
phperだってパンチカードでコーディングしたい
o0h
PRO
0
370
[JA] Domain Modeling with Vanilla Rails: 「操作」から再考するRailsの設計論
yasaichi
3
660
sbt 2.0: takeoff
eed3si9n
0
160
作って学ぶSpring BootのAuto Configuration / JJUG Night Seminar 2026-10-19
rshindo
0
190
AI-Powered Domain-Driven Design
ziobrando
0
140
機械に任せるテスト、人が見るテスト ⽣成AIで引き直した、運⽤保守フェーズの線引き
dske104
0
190
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Kaigi on Rails 2026, Tokyo, Japan
marcoroth
2
300
Optunaを作ったときにやりたかったこと、その後 (2026/10/16, Optuna Meetup #3)
iwiwi
2
1.1k
ソニーのクラウド共通基盤の変遷とAI時代の開発スタイルに合わせた進化 / The Journey of Sony’s Common Cloud Platform and Its Evolution for AI-Native Development
kenjiyoneyama
0
290
Featured
See All Featured
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
2
6.3k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
630
The Illustrated Children's Guide to Kubernetes
chrisshort
51
53k
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
2
410
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.9k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3.1k
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
570
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
46k
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
320
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2.2k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
Thoughts on Productivity
jonyablonski
76
5.4k
Transcript
GABRIELE PETRONELLA MONAD TRANSFORMERS DOWN TO EARTH
ME, HI! @gabro27 Scala Swarm 2017
STUFF I DO @gabro27 Scala Swarm 2017
THIS TALK: WHAT AND WHY @gabro27 Scala Swarm 2017
THIS TALK: WHAT AND WHY What: The talk I wished
I attended before banging my head against this @gabro27 Scala Swarm 2017
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 Scala Swarm 2017
A QUESTION @gabro27 Scala Swarm 2017
@gabro27 Scala Swarm 2017
THE PROBLEM val x: Future[List[Int]] = ??? futureList.map(list => list.map(f))
^ ^ |________________| 2 maps 1 function @gabro27 Scala Swarm 2017
CAN WE DO BETTER? @gabro27 Scala Swarm 2017
INDENT! futureList.map { list => list.map(f) } @gabro27 Scala Swarm
2017
None
FUNCTOR trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A =>
B): F[B] } @gabro27 Scala Swarm 2017
FUNCTOR OF FUTURE val futureF = new Functor[Future] { def
map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f) } @gabro27 Scala Swarm 2017
future.map(f) list.map(f) | | | | Functor[Future].map(future)(f) Functor[List].map(list)(f) @gabro27 Scala
Swarm 2017
futureList.map(f) // not really valid scala | // but you
get the point | Functor[Future[List]].map(futureList)(f) @gabro27 Scala Swarm 2017
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 Scala Swarm 2017
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 Scala Swarm 2017
CATS https://github.com/typelevel/cats @gabro27 Scala Swarm 2017
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 Scala Swarm 2017
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 Scala Swarm 2017
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 Scala Swarm 2017
THE 'M' WORD trait Monad[F[_]] { def pure[A](a: A): F[A]
def map[A, B](fa: F[A])(f: A => B): F[B] def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] } @gabro27 Scala Swarm 2017
THE 'M' WORD trait Monad[F[_]] { def pure[A](a: A): F[A]
def map[A, B](fa: F[A])(f: A => B): F[B] def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] } @gabro27 Scala Swarm 2017
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 Scala Swarm 2017
LET'S COMPREHEND THIS val getCity: Future[String] = for { gab
<- getUser("Gabriele") address <- getAddress(gab) } yield address.city @gabro27 Scala Swarm 2017
LESSONS monads allow sequential execution monads can squash F[F[A]] into
F[A] @gabro27 Scala Swarm 2017
QUESTIONS? @gabro27 Scala Swarm 2017
WAIT A SECOND... @gabro27 Scala Swarm 2017
WHAT ABOUT F[G[X]] @gabro27 Scala Swarm 2017
BACK TO THE REAL WORLD def getUser(name: String): Future[User] //
<- really? def getAddress(user: User): Future[Address] @gabro27 Scala Swarm 2017
BACK TO THE REAL WORLD def getUser(name: String): Future[Option[User]] //
better def getAddress(user: User): Future[Option[Address]] @gabro27 Scala Swarm 2017
UH, OH... val city: Future[Option[String]] = for { gab <-
getUser("Gabriele") address <- getAddress(gab) // ! FAIL } yield address.city @gabro27 Scala Swarm 2017
EVENTUALLY val city: Future[Option[String]] = for { gab <- getUser("Gabriele")
address <- getAddress(gab.get) // ! } yield address.get.city // ! @gabro27 Scala Swarm 2017
OR... val city: Future[Option[String]] = for { maybeUser <- getUser("Gabriele")
maybeCity <- maybeUser match { case Some(user) => getAddress(user).map(_.map(_.city)) case None => Future.successful(None) } } yield maybeCity @gabro27 Scala Swarm 2017
WHAT WE WOULD REALLY WANT val city: Future[Option[String]] = for
{ gab <- maybeUser <- getUser("Gabriele") address <- maybeAddress <- getAddress(gab) } yield address.city @gabro27 Scala Swarm 2017
futureUser.flatMap(f) maybeUser.flatMap(f) | | | | Monad[Future].flatMap(futureUser)(f) Monad[Option].flatMap(maybeUser)f) @gabro27 Scala
Swarm 2017
futureMaybeUser.flatMap(f) | | Monad[Future[Option]].flatMap(f) @gabro27 Scala Swarm 2017
futureMaybeUser.flatMap(f) | | Monad[Future[Option]].flatMap(f) @gabro27 Scala Swarm 2017
MONADS DO NOT COMPOSE HTTP://BLOG.TMORRIS.NET/POSTS/ MONADS-DO-NOT-COMPOSE/ @gabro27 Scala Swarm 2017
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 Scala Swarm 2017
MONADS DO NOT COMPOSE GENERICALLY @gabro27 Scala Swarm 2017
BUT YOU CAN COMPOSE THEM SPECIFICALLY @gabro27 Scala Swarm 2017
flatMap FOR Future[Option[A]] val city: Future[Option[String]] = for { maybeUser
<- getUser("Gabriele") maybeCity <- maybeUser match { case Some(user) => getAddress(user).map(_.map(_.city)) case None => Future.successful(None) } } yield maybeCity @gabro27 Scala Swarm 2017
THE MONAD INTERFACE trait Monad[F[_]] { def pure[A](a: A): F[A]
def map[A, B](fa: F[A])(f: A => B): F[B] def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] } @gabro27 Scala Swarm 2017
case class FutOpt[A](value: Future[Option[A]) @gabro27 Scala Swarm 2017
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 Scala Swarm 2017
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 Scala Swarm 2017
WHAT IF def getUsers(query: String): List[Option[User]] @gabro27 Scala Swarm 2017
case class ListOpt[A](value: List[Option[A]) @gabro27 Scala Swarm 2017
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 Scala Swarm 2017
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 Scala Swarm 2017
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 Scala Swarm 2017
A MORE GENERIC APPROACH case class WhateverOpt[A, W[_]](value: W[Option[A]]) @gabro27
Scala Swarm 2017
MEET OptionT OptionT[F[_], A] ^ |___ any monad @gabro27 Scala
Swarm 2017
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 Scala Swarm 2017
IN GENERAL Foo[Bar[X]] becomes BarT[Foo, X] @gabro27 Scala Swarm 2017
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 Scala Swarm 2017
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 Scala Swarm 2017
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 Scala Swarm 2017
EXAMPLE: UPDATING A USER > check user exists > check
it can be updated > update it @gabro27 Scala Swarm 2017
THE NAIVE WAY def checkUserExists(id: String): Future[Option[User]] def checkCanBeUpdated(u: User):
Future[Boolean] def updateUserOnDb(u: User): Future[User] @gabro27 Scala Swarm 2017
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 Scala Swarm 2017
DETAILED ERRORS from Option[User] to Either[MyError, User] @gabro27 Scala Swarm
2017
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 Scala Swarm 2017
! F[Either[A, B]] @gabro27 Scala Swarm 2017
! EitherT[F[_], A, B] @gabro27 Scala Swarm 2017
HOW ABOUT case class MyError(msg: String) type ResultT[F[_], A] =
EitherT[F, MyError, A] type FutureResult[A] = ResultT[Future, A] @gabro27 Scala Swarm 2017
SOME HELPERS object FutureResult { def apply[A](a: A): FutureResult[A] =
apply(Future.successful(a)) def apply[A](fa: Future[A]): FutureResult[A] = EitherT.liftT(fa) def apply[A](e: Either[MyError, A]): FutureResult[A] = EitherT.fromEither(e) } @gabro27 Scala Swarm 2017
def checkUserExists(id: String): FutureResult[User] = FutureResult { if (id ===
"123") User("123").asRight else MyError("sorry, no user").asLeft } def checkCanBeUpdated(u: User): FutureResult[Unit] = def updateUserOnDb(u: User): FutureResult[User] = ??? @gabro27 Scala Swarm 2017
BETTER? def updateUser(user: User): FutureResult[User] = for { user <-
checkUserExists(user.id) _ <- checkCanBeUpdated(user) updatedUser <- updateUser(user) } yield updatedUser @gabro27 Scala Swarm 2017
PERSONAL TIPS @gabro27 Scala Swarm 2017
TIP #1 stacking more than two monads gets bad really
quickly @gabro27 Scala Swarm 2017
EXAMPLE1 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 1 from djspiewak/emm @gabro27 Scala Swarm 2017
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 Scala Swarm 2017
TIP #3 ! Perf! Wrapping/unwrapping isn't cheap, so if you're
concerned about performance, consider benchmarking your code. @gabro27 Scala Swarm 2017
TIP #4 Use them as a ""local optimization"". In case
your problem is not "local", consider alternative approaches. @gabro27 Scala Swarm 2017
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 working with stacked monads @gabro27 Scala Swarm 2017
WHAT ELSE? @gabro27 Scala Swarm 2017
FREE MONADS > clearly separate structure and interpretation > effects
are separated from program definition http://typelevel.org/cats/datatypes/freemonad.html @gabro27 Scala Swarm 2017
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 Scala Swarm 2017
None
questionsT @gabro27 @buildoHQ @scalaitaly @gabro27 Scala Swarm 2017