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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Alexey Novakov
March 06, 2020
Programming
110
0
Share
Monads in Scala
Monad as an abstract interface. Usage of Monads in Scala
Alexey Novakov
March 06, 2020
More Decks by Alexey Novakov
See All by Alexey Novakov
Особенности разработки Flink джобов на Scala
alexeyn
0
60
Streamhouse Architecture with Flink and Paimon
alexeyn
0
300
Flink Forward 2023: State of Scala API in Apache Flink
alexeyn
0
180
State of Scala API in Apache Flink
alexeyn
0
430
Deployment of Streaming Application with Ververica Platform on Kubernetes
alexeyn
0
250
Rapid Deployment with Apache Flink and Ververica Platform
alexeyn
0
180
Scalacon 2021 - Deep Learning in Scala
alexeyn
0
120
AWS Airflow and EMR
alexeyn
0
130
EMR Data Ingestion with Apache Hudi
alexeyn
0
50
Other Decks in Programming
See All in Programming
CursorとClaudeCodeとCodexとOpenCodeを実際に比較してみた
terisuke
1
490
PHPer、Cloudflare に引っ越す
suguruooki
1
100
煩雑なSkills管理をSoC(関心の分離)により解決する――関心を分離し、プロンプトを部品として育てるためのOSSを作った話 / Solving Complex Skills Management Through SoC (Separation of Concerns)
nrslib
4
990
AIと共に生きる技術選定 2026
sgash708
0
100
検索設計から 推論設計への重心移動と Recall-First Retrieval
po3rin
3
990
属人化しないコード品質の作り方_2026.04.07.pdf
muraaano
0
230
PicoRuby for IoT: Connecting to the Cloud with MQTT
yuuu
2
640
forteeの改修から振り返るPHPerKaigi 2026
muno92
PRO
3
290
エラー処理の温故知新 / history of error handling technic
ryotanakaya
0
120
Terraform言語の静的解析 / static analysis of Terraform language
wata727
1
110
ハーネスエンジニアリングとは?
kinopeee
12
6k
10年分の技術的負債、完済へ ― Claude Code主導のAI駆動開発でスポーツブルを丸ごとリプレイスした話
takuya_houshima
0
2.6k
Featured
See All Featured
The SEO Collaboration Effect
kristinabergwall1
1
430
Why Our Code Smells
bkeepers
PRO
340
58k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
69
39k
sira's awesome portfolio website redesign presentation
elsirapls
0
220
Statistics for Hackers
jakevdp
799
230k
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
1
2k
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
320
Done Done
chrislema
186
16k
Color Theory Basics | Prateek | Gurzu
gurzu
0
290
Discover your Explorer Soul
emna__ayadi
2
1.1k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
55k
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
250
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?