Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Monads in Scala
Search
Alexey Novakov
March 06, 2020
Programming
0
100
Monads in Scala
Monad as an abstract interface. Usage of Monads in Scala
Alexey Novakov
March 06, 2020
Tweet
Share
More Decks by Alexey Novakov
See All by Alexey Novakov
Особенности разработки Flink джобов на Scala
alexeyn
0
35
Streamhouse Architecture with Flink and Paimon
alexeyn
0
270
Flink Forward 2023: State of Scala API in Apache Flink
alexeyn
0
160
State of Scala API in Apache Flink
alexeyn
0
410
Deployment of Streaming Application with Ververica Platform on Kubernetes
alexeyn
0
220
Rapid Deployment with Apache Flink and Ververica Platform
alexeyn
0
150
Scalacon 2021 - Deep Learning in Scala
alexeyn
0
100
AWS Airflow and EMR
alexeyn
0
120
EMR Data Ingestion with Apache Hudi
alexeyn
0
41
Other Decks in Programming
See All in Programming
Vibe Coding - AI 驅動的軟體開發
mickyp100
0
180
高速開発のためのコード整理術
sutetotanuki
1
400
登壇資料を作る時に意識していること #登壇資料_findy
konifar
4
1.1k
Spinner 軸ズレ現象を調べたらレンダリング深淵に飲まれた #レバテックMeetup
bengo4com
1
230
CSC307 Lecture 03
javiergs
PRO
1
490
CSC307 Lecture 05
javiergs
PRO
0
500
コントリビューターによるDenoのすゝめ / Deno Recommendations by a Contributor
petamoriken
0
200
今こそ知るべき耐量子計算機暗号(PQC)入門 / PQC: What You Need to Know Now
mackey0225
3
370
[KNOTS 2026登壇資料]AIで拡張‧交差する プロダクト開発のプロセス および携わるメンバーの役割
hisatake
0
280
開発者から情シスまで - 多様なユーザー層に届けるAPI提供戦略 / Postman API Night Okinawa 2026 Winter
tasshi
0
200
副作用をどこに置くか問題:オブジェクト指向で整理する設計判断ツリー
koxya
1
610
FOSDEM 2026: STUNMESH-go: Building P2P WireGuard Mesh Without Self-Hosted Infrastructure
tjjh89017
0
170
Featured
See All Featured
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
1
120
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
85
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.1k
A better future with KSS
kneath
240
18k
Done Done
chrislema
186
16k
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3k
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
580
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
160
Into the Great Unknown - MozCon
thekraken
40
2.3k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
120
Transcript
Monads in Scala Alexey Novakov, Ultra Tendency, 2020
"A monad is just a monoid in the category of
endofunctors”
“Monad is an abstract interface” Book: Functional Programming in Scala
(Red Book)
Option Either List Future Map Set Stream Vector Future Try
And others … Monads in standard Scala
flatMap unit a.k.a bind a.k.a pure What makes thing a
Monad? “apply” in Scala
class Box[A](v: A) { def flatMap[B](f: A => Box[B]): Box[B]
= f(v) } def map[B](f: A => B): Box[B] = flatMap(a => new Box(f(a))) A Monad modeled as a class minimum set
scala> new Box(1) res2: Box[Int] = 1 scala> res2.map(_ +
1) res3: Box[Int] = 2 scala> res3.flatMap(i => new Box(1 + i)) res5: Box[Int] = 3
scala> val l = List(1,2,3) l: List[Int] = List(1, 2,
3) scala> l.map(_ + 1) res0: List[Int] = List(2, 3, 4) scala> l.flatMap(i => List(i + 1)) res1: List[Int] = List(2, 3, 4) List
val isOn = Some(1) val isBlack = None def makeCoffee:
Option[String] = Some(1) scala> isOn .flatMap(_ => isBlack .flatMap(_ => makeCoffee)) res0: Option[String] = None Option stopped here
trait Functor[F[_]] { def map[A,B](fa: F[A])(f: A => B): F[B]
} trait Monad[F[_]] extends Functor[F] { def unit[A](a: => A): F[A] def flatMap[A,B](ma: F[A])(f: A => F[B]): F[B] def map[A,B](ma: F[A])(f: A => B): F[B] = flatMap(ma)(a => unit(f(a))) } Generic Monad another abstract interface
Monads are like burritos
“f” function application in flatMap & map depends on the
concrete Monad instance “f” applied when: - Option[A]: is Some(A) - Either[A, B]: is Right(B) - List[A]: is non-empty - Future[A]: is ready
Moreover, Monad laws are there
Monad law 1. Identity Example def f(x: Int): Option[Int] =
Some(x) scala> Some(1).flatMap(f) == f(1) res0: Boolean = true scala> f(1) == Some(1).flatMap(f) res1: Boolean = true
Monad law 1. Left Identity def f[A](x: A): Monad[A] =
??? flatMap(unit(x))(f) == f(x) Right Identity f(x) == flatMap(unit(x))(f)
Monad law 2. Associative Example def f1(a: Int): Option[Int] =
Some(a + 1) def f2(a: Int): Option[Int] = Some(a * 2) scala> Some(1).flatMap(f1).flatMap(f2) res0: Option[Int] = Some(4) scala> Some(1).flatMap(a => f1(a).flatMap(f2)) res1: Option[Int] = Some(4)
Monad law 2. Associative def f1[A](a: A): Monad[A] def f2[A](a:
A): Monad[A] if x is a Monad instance, flatMap(flatMap(x)(f1))(f2) == flatMap(x)(a => flatMap(f1(a))(f2))
Functor law 1. Identity map(x)(a => a) == x Example:
map(Some(1))(a => a) == Some(1) the same value returned
Functor law 2. Associative Example: val f1 = (n: Int)
=> n + 1 val f2 = (n: Int) => n * 2 map(map(Some(1))(f1))(f2) // Some(4) == map(Some(1))(f2 compose f1) // Some(4)
Functor law 2. Associative map(map(x)(f1))(f2) == map(x)(f2 compose f1) one
by one apply f1 then f2 in one step
None
final case class Coffee(name: String) val isOn = Some(1) val
coffeeName = Some("black") val makeCoffee = (name: String) => Some(Coffee(name)) for { _ <- isOn name <- coffeeName coffee <- makeCoffee(name) } yield coffee scala> Op)on[Coffee] = Some(Coffee(black)) Compose Option
case class Cluster(pods: Int) def validateNamespace(ns: String): Either[String, Unit] =
Right(()) def clusterExists(ns: String): Either[Cluster, Unit] = Right(()) def createCluster(ns: String, cluster: Cluster): Either[String, Cluster] = Right(Cluster(cluster.pods)) val ns = "my-cluster" for { _ <- validateNamespace(ns) _ <- clusterExists(ns).left.map(c => s"Cluster with ${c.pods} pods already exists") newCluster <- createCluster(ns, Cluster(4)) } yield newCluster Compose Either scala> Either[String,Cluster] = Right(Cluster(4))
scala> Either[String,Cluster] = LeC( Cluster namespace is not valid name,
choose another name ) def validNamespace(ns: String): Either[String, Unit] = if (ns == "my-cluster") Left( “Cluster namespace is not valid name, choose another name” ) else Right(())
for {…} yield is a syntactic sugar for a sequence
of calls: flatMap1(… + flatMapN(.. + map(…))) https://en.wikipedia.org/wiki/Powdered_sugar#/media/File:Powdered_Sugar_-_Macro.jpg
validNamespace("my-cluster") .flatMap(_ => clusterExists(ns) .left .map(c => s"Cluster with ${c.pods}
pods already exists” ).flatMap(_ => createCluster(ns, Cluster(4)) .map(newCluster => newCluster) ) ) Desugared
for { _ <- validNamespace("my-cluster") _ <- clusterExists(ns) .left.map(c =>
s"Cluster with ${c.pods} pods already exists") newCluster <- createCluster(ns, Cluster(4)) } yield newCluster Sugared
Caveat: different Monads do not compose.
def validateNamespace(ns: String): Either[String, Unit] def clusterExists(ns: String): Option[Either[String, Cluster]]
def createCluster(ns: String, cluster: Cluster): Either[String, Cluster] Problem for { _ <- validateNamespace(ns) cluster <- clusterExists(ns) updated <- createCluster(ns, cluster) } yield updated two monad stacks
updated <- createCluster(ns, cluster) ^ <pas)e>:4: error: type mismatch; found
: Either[String,Cluster] required: Cluster cluster <- clusterExists(ns) ^ <pas)e>:3: error: type mismatch; found : Op)on[Nothing] required: scala.u)l.Either[?,?] Op)on[Nothing] <: scala.u)l.Either[?,?]? false
Monad Transformers
- custom-written monad - specifically constructed for composition OptionT: Option
+ Any other Monad EitherT: Either + Any other Monad ReaderT: Reader + Any other Monad WriterT: Writer + Any other Monad … others
EitherT *Example: unwrap second stack * from cats.data.EitherT.scala final case
class EitherT[F[_], A, B](value: F[Either[A, B]]) { def flatMap[AA >: A, D]( f: B => EitherT[F, AA, D])( implicit F: Monad[F]): EitherT[F, AA, D] = EitherT(F.flatMap(value) { case l @ Left(_) => F.pure(l.rightCast) case Right(b) => f(b).value }) }
case class Cluster(pods: Int, updated: Long) def validateNamespace(ns: String): Either[String,
Unit] = Right(()) def clusterExists(ns: String): Option[Either[String, Cluster]] = Some(Right(Cluster(3, System.currentTimeMillis()))) def updateCluster(ns: String, cluster: Cluster): Either[String, Cluster] = Right(Cluster(cluster.pods, System.currentTimeMillis())) Usage
import cats.implicits._ import cats.data.EitherT val cluster = for { _
<- validateNamespace(ns).toEitherT[Option] cluster<- EitherT(clusterExists(ns)) updated <- updateCluster(ns, cluster).toEitherT[Option] } yield updated scala> cluster.value Some(Right(Cluster(3,1583095558496))) EitherT: Either + Option common ground
Error case def clusterExists(ns: String): Option[Either[String, Cluster]] = Left("Cluster is
invalid").some scala> cluster.value res4: Op)on[Either[String,Cluster]] = Some(LeC(Cluster is invalid))
Alexey Novakov email: - alexey.novakov at ultratendency.com - novakov.alex at
gmail.com Blog: https://medium.com/se-notes-by-alexey-novakov https://novakov-alexey.github.io/ Code: https://github.com/novakov-alexey Twitter: @alexey_novakov Thank you! Questions?