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
53
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 Forward 2023: State of Scala API in Apache Flink
alexeyn
0
50
State of Scala API in Apache Flink
alexeyn
0
190
Deployment of Streaming Application with Ververica Platform on Kubernetes
alexeyn
0
100
Rapid Deployment with Apache Flink and Ververica Platform
alexeyn
0
41
Scalacon 2021 - Deep Learning in Scala
alexeyn
0
46
AWS Airflow and EMR
alexeyn
0
37
EMR Data Ingestion with Apache Hudi
alexeyn
0
15
Data Stream Processing with AWS Kinesis
alexeyn
0
94
Deep Learning in Scala 3 from scratch
alexeyn
0
370
Other Decks in Programming
See All in Programming
Clean Architecture by TypeScript & NestJS
ryounasso
0
150
Prompt FlowによるLLMアプリケーション開発
yuto2000
1
1k
DMMプラットフォームにおけるTiDBの導入から運用まで
pospome
7
3k
Introduction to GitOps
hwchiu
0
110
CSC307 Lecture 09
javiergs
PRO
1
500
Jetpack for KMP
fornewid
1
290
CSC307 Lecture 12
javiergs
PRO
0
220
iOSアプリでクリップボードにコピーしたことをユーザーに伝えるちょうど良いフィードバックを探す
ski
0
100
DDDを志して3年経ったら「DDDの皮を被ったクリーンアーキテクチャ」になった話【デブサミ2024夏】
texmeijin
1
620
Google's Recipe for Scaling (Web) Security – LocoMocoSec 2024
lweichselbaum
0
170
Xcode 16のPreviewModifierと@Previewableを活用した効率的なプレビュー方法の考察
ojun9
2
160
HMSコンペ 11th Solution (team : kansai-kaggler)
t88
1
680
Featured
See All Featured
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
23
1.9k
Designing with Data
zakiwarfel
96
5k
RailsConf 2023
tenderlove
16
720
XXLCSS - How to scale CSS and keep your sanity
sugarenia
245
1.2M
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
129
32k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
17
8.7k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
277
13k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
26
2.1k
Done Done
chrislema
179
15k
Navigating Team Friction
lara
181
13k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
224
21k
Robots, Beer and Maslow
schacon
PRO
157
8.1k
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?