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
110
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
51
Streamhouse Architecture with Flink and Paimon
alexeyn
0
290
Flink Forward 2023: State of Scala API in Apache Flink
alexeyn
0
180
State of Scala API in Apache Flink
alexeyn
0
420
Deployment of Streaming Application with Ververica Platform on Kubernetes
alexeyn
0
240
Rapid Deployment with Apache Flink and Ververica Platform
alexeyn
0
170
Scalacon 2021 - Deep Learning in Scala
alexeyn
0
110
AWS Airflow and EMR
alexeyn
0
130
EMR Data Ingestion with Apache Hudi
alexeyn
0
47
Other Decks in Programming
See All in Programming
AI 開発合宿を通して得た学び
niftycorp
PRO
0
180
PHP でエミュレータを自作して Ubuntu を動かそう
m3m0r7
PRO
2
150
Nuxt Server Components
wattanx
0
140
ファインチューニングせずメインコンペを解く方法
pokutuna
0
200
AI活用のコスパを最大化する方法
ochtum
0
340
守る「だけ」の優しいEMを抜けて、 事業とチームを両方見る視点を身につけた話
maroon8021
3
1.5k
AIコードレビューの導入・運用と AI駆動開発における「AI4QA」の取り組みについて
hagevvashi
0
570
我々はなぜ「層」を分けるのか〜「関心の分離」と「抽象化」で手に入れる変更に強いシンプルな設計〜 #phperkaigi / PHPerKaigi 2026
shogogg
2
640
今からFlash開発できるわけないじゃん、ムリムリ! (※ムリじゃなかった!?)
arkw
0
160
飯MCP
yusukebe
0
390
ネイティブアプリとWebフロントエンドのAPI通信ラッパーにおける共通化の勘所
suguruooki
0
210
GC言語のWasm化とComponent Modelサポートの実践と課題 - Scalaの場合
tanishiking
0
130
Featured
See All Featured
SEO for Brand Visibility & Recognition
aleyda
0
4.4k
Git: the NoSQL Database
bkeepers
PRO
432
67k
Visualization
eitanlees
150
17k
Exploring anti-patterns in Rails
aemeredith
2
290
Navigating the Design Leadership Dip - Product Design Week Design Leaders+ Conference 2024
apolaine
0
250
Into the Great Unknown - MozCon
thekraken
40
2.3k
Leading Effective Engineering Teams in the AI Era
addyosmani
9
1.8k
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
140
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
2.6k
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.2k
Writing Fast Ruby
sferik
630
63k
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
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?