SlideShare a Scribd company logo
PRACTICAL CATS
sharing what i’ve learnt
ABOUT MYSELF
Practical cats
CATS….
To use cats effectively, understand what each
construct does:
Functor
Monads
Applicatives
Monoids
Semigroups
SO MANY ACRONYMS !!!
Validated
Practical cats
Building blocks …
Understand that they are building blocks

so that you can write code that is pure and
code that has side-effects — separation of
concerns.
Typeclasses …
Each of the type class (e.g. functors,
monoids, monads etc) are governed by laws.
Typeclasses! 

they are behaviours that can be “inherited”

by your code.
Semigroups - what are they?
trait Semigroup[A] {

def combine(x: A, y: A) : A

}
 general structure to define things 

that can be combined.

*Cats provides “default” implementations; developers 

(like you & me) need to provide implementations that conform to the traits. *
Monoids - what are they?
trait Monoid[A] extends Semigroup[A] {

def empty: A

def combine(x: A, y: A) : A

}
 general structure to define things 

that can be combined and has a “default”

element.

*Cats provides “default” implementations; developers 

(like you & me) need to provide implementations that conform to the traits. *
Monoids - what are they?
> import cats._, data._, implicits._

> Monoid[String].combine(“hi”, “there”)

// res0: String = “hithere”

> “hi” |+| “there”

// res1: String = “hithere”
Use case for Monoids/Semigroups
They’re good for combining 2 or more things of a similar
nature

data-type-a data-type-b
data-stream end-
point
parser
collector
of either data-type-a or
data-type-b
Use case #1 - Monoids for “smashing” values
* all names used here do not reflect the actuals *
// Monoid[DataTypeAB] defined somewhere else
def buildDataFromStream(datatypeA : DataTypeA,
datatypeB : DataTypeB,
accumulator: DatatypeAB) =
validateData(datatypeA, datatypeB).fold(
onError => {
// `orError` is lifted into the datatype
val errors = Monoid[DatatypeAB].empty.copy(lift(onError))
Monoid[DatatypeAB].combine(accumulator, errors)
},
parsedValue => {
// `parsedValue` is lifted into the datatype
val newValue = Monoid[DatatypeAB].empty.copy(lift(parsedValue))
Monoid[DatatypeAB].combine(accumulator, newValue)
}
)
Functors - what are they?
trait Functor[F[_]] {

def map[A,B](fa: F[A])(f: A => B) : F[B]

}

general structure to represent something

that can be mapped over. If you’ve been using Lists

, Options, Eithers, Futures in Scala, you’ve been using
functors.

!!! They are very common structures indeed ☺ !!!
* functors are used in clever things like recursion-schemes *
Functors - what are they?
> import cats._, data._, implicits._

> Functor[List].lift((x:Int) => x + 1)

// res0: List[Int] => List[Int]

> res0(List(1))

// res1: List[Int] = List(2)
* Nugget of info: Functors preserve “structure” *
Monads
Monads are meant for 

sequencing computations
Monads
someList.flatmap(element => 

someOtherList.flatmap(element2 =>

(element, element2)))
*No tuples generated if either “someList”

Or “someOtherList” is empty*
Monads
someList.flatmap(element => 

someOtherList.flatmap(element2 =>

(element, element2)))
Monads allows for short-circuiting of

computations
Monads - a quick summary?
Writers - information can be carried along with
the computation

Readers - compose operations that depend

on some input.

State - allows state to be “propagated”

Eval - abstracts over eager vs lazy evaluation.
Monads - examples
> List(1,2,3) >>= (value => List(value+1))

> res0: List[Int] = List(2,3,4)
def >>=[A,B](fa: F[A])(f:A => F[B]): F[B] =
flatMap(fa)(f)
“>>=“ is also known as “bind” (in Cats, its really “flatMap”)
Monads - examples
> Monad[List].lift((x:Int) => x + 1)(List(1,2,3))

> res1: List[Int] = List(2,3,4)
Typeclasses allows you to define re-usable code by
lifting functions.
Monads - examples
> Monad[List].pure(4)

> res2: List[Int] = List(4)
This is to lift values into a context, in this case
Monadic context.
Monads - flow
scala> def first = Reader( (x:Int) => Monad[List].ifM(List(true,true))(x::Nil, Nil) )

extractGroups: cats.data.Reader[Int,List[Int]]

scala> def second = Reader( (x:Int) => Monad[List].ifM(List(true,true))(x::Nil, Nil) )

loadGroup: cats.data.Reader[Int,List[Int]]

scala> for { g <- first(4); gg <- second(g) } yield gg

res21: List[Int] = List(4, 4, 4, 4)
Writer Monad
“Writers” are typically used to carry not only the value
of a computation but also some other information (typically, its
used to carry logging info).
source: http://eed3si9n.com/herding-cats/Writer.html
Writer Monad
scala> def logNumber(x: Int): Writer[List[String], Int] =
         Writer(List("Got number: " + x.show), 3)
logNumber: (x: Int)cats.data.Writer[List[String],Int]
scala> def multWithLog: Writer[List[String], Int] =
         for {
           a <- logNumber(3)
           b <- logNumber(5)
         } yield a * b
multWithLog: cats.data.Writer[List[String],Int]
scala> multWithLog.run
res2: cats.Id[(List[String], Int)] = (List(Got number: 3, Got number: 5),9)
scala> multWithLog.reset
res6: cats.data.WriterT[cats.Id,List[String],Int] = WriterT((List(),9))
scala> multWithLog.swap
res8: cats.data.WriterT[cats.Id,Int,List[String]] = WriterT((9,List(Got number: 4, Got
number: 3)))
scala> multWithLog.value
res9: cats.Id[Int] = 9
scala> multWithLog.written
res10: cats.Id[List[String]] = List(Got number: 4, Got number: 3)
source: http://eed3si9n.com/herding-cats/Writer.html
Compose Writers
Reader Monad
“Readers” allows us to compose operations which depend
on some input.
source: http://eed3si9n.com/herding-cats/Reader.html
Reader Monad
case class Config(setting: String, value: String)
def getSetting = Reader {
(config: Config) => config.setting
}
def getValue = Reader {
(config: Config) => config.value
}
for {
s <- getSetting
v <- getValue
} yield Config(s, v)
Compose Readers
FP-style to abstract and
encapsulate.
State Monad
Allows us to pass state-information around in a computation.
http://eed3si9n.com/herding-cats/State.html
Use case #3 - Reader + State Monad
def process: Reader[Elem, Seq[Mapping]] = Reader {
(xml: Elem) =>
for {
groups <- extractGroups(dataXml).toOption
group <- groups
grpCfg <- loadGroupConfig(group).toOption
stateObj <- ConfigState(grpCfg).pure[Option]
records <- loadRecords(group).toOption
record <- records
row <- processRecord(i)(stateObj)(record).pure[Option]
} yield {
// processing …
}
}
case class ConfigState(init: Config) {
private[this] var currentState: Config = init
def storeCfg : State[Config, Config] =
State{ (cfg: Config) =>
val prevState = currentState
currentState = cfg
(currentState, prevState) }
def loadCfg : Config =
( for {
s <- State.get[Config]
} yield s ).runA(currentState).value
}
Use case #3 - Reader + State Monad
def process: Reader[Elem, Seq[Mapping]] = Reader {
(xml: Elem) =>
for {
groups <- extractGroups(dataXml).toOption
group <- groups
grpCfg <- loadGroupConfig(group).toOption
stateObj <- ConfigState(grpCfg).pure[Option]
records <- loadRecords(group).toOption
record <- records
row <- processRecord(i)(stateObj)(record).pure[Option]
} yield {
// processing …
}
}
case class ConfigState(init: Config) {
private[this] var currentState: Config = init
def storeCfg : State[Config, Config] =
State{ (cfg: Config) =>
val prevState = currentState
currentState = cfg
(currentState, prevState) }
def loadCfg : Config =
( for {
s <- State.get[Config]
} yield s ).runA(currentState).value
}
Separation of concerns
State management
Applicative
Applicatives allows for functions to be
lifted over a structure (Functor).

Because the function and the value it’s being applied 

to both have structures, hence its needs to be
combined.
Applicative - examples
scala> Applicative[List].lift((x:Int) => x + 1)
res1: List[Int] => List[Int] = <function1>
scala> Applicative[List].lift(
| (x:List[Int=>Int]) =>
| x.map(f => f(2)))
| (List( List((x:Int) => x + 1 )))
res7: List[List[Int]] = List(List(3))
scala> val fs = List(List((x:Int) => x + 1))
fs: List[List[Int => Int]] = List(List(<function1>))
scala> fs.map(_(2))
res15: cats.data.Nested[List,List,Int] =
Nested(List(List(3)))
Applicative is like a Functor
Applicative - examples
scala> Applicative[List].lift((x:Int) => x + 1)
res1: List[Int] => List[Int] = <function1>
scala> Applicative[List].lift(
| (x:List[Int=>Int]) =>
| x.map(f => f(2)))
| (List( List((x:Int) => x + 1 )))
res7: List[List[Int]] = List(List(3))
scala> val fs = List(List((x:Int) => x + 1))
fs: List[List[Int => Int]] = List(List(<function1>))
scala> fs.map(_(2))
res15: cats.data.Nested[List,List,Int] =
Nested(List(List(3)))
Applicative is like a Functor
Applying a function which is nested.
Applicative - examples
scala> Applicative[List].lift((x:Int) => x + 1)
res1: List[Int] => List[Int] = <function1>
scala> Applicative[List].lift(
| (x:List[Int=>Int]) =>
| x.map(f => f(2)))
| (List( List((x:Int) => x + 1 )))
res7: List[List[Int]] = List(List(3))
scala> val fs = List(List((x:Int) => x + 1))
fs: List[List[Int => Int]] = List(List(<function1>))
scala> fs.map(_(2))
res15: cats.data.Nested[List,List,Int] =
Nested(List(List(3)))
Applicative is like a Functor
Applying a function which is nested.
Cat has a “Nested” to achieve the same.
Applicative - examples
A typical application is to leverage Applicatives in writing
Logic to validate configurations, forms etc
import cats.Cartesian
import cats.data.Validated
import cats.instances.list._ // Semigroup for List
type AllErrorsOr[A] = Validated[List[String], A]
Cartesian[AllErrorsOr].product(
Validated.invalid(List("Error 1")),
Validated.invalid(List("Error 2")) )
// res1: AllErrorsOr[(Nothing, Nothing)] = Invalid(List(Error 1,Error 2))
Applicative - examples
package xxx.config
import scala.concurrent.duration.{Duration,FiniteDuration}
import cats._
import cats.data._
import cats.implicits._
import cats.data.Validated
import cats.data.Validated.{Invalid, Valid}
// code that needs to remain hidden
sealed abstract class ConfigError
final case class MissingConfig(field : String) extends ConfigError
final case class ParseError(field: String) extends ConfigError
case class Config(map : Map[String,String])
case class HuffConfig(
clusterName: String,
clusterPort : Int,
clusterAddress : String,
hostname: String,
listeningPort: Int)
object Validator {
def getHuffConfig(config: Config) : ValidatedNel[ConfigError, HuffConfig] =
Apply[ValidatedNel[ConfigError, ?]].map5(
config.parse[String] ("DL_CLUSTER_NAME").toValidatedNel,
config.parse[Int] ("DL_CLUSTER_PORT").toValidatedNel,
config.parse[String] ("DL_CLUSTER_ADDRESS").toValidatedNel,
config.parse[String] ("DL_HTTP_ADDRESS").toValidatedNel,
config.parse[Int] ("DL_HTTP_PORT").toValidatedNel) {
case (clusterName, clusterPort, clusterAddress, httpAddr, httpPort) =>
HuffConfig(clusterName, clusterPort, clusterAddress, httpAddr, httpPort)
}
}
package xxx.config
import scala.concurrent.duration.{Duration,FiniteDuration}
import cats._
import cats.data._
import cats.implicits._
import cats.data.Validated
import cats.data.Validated.{Invalid, Valid}
// code that needs to remain hidden
sealed abstract class ConfigError
final case class MissingConfig(field : String) extends ConfigError
final case class ParseError(field: String) extends ConfigError
case class Config(map : Map[String,String])
case class HuffConfig(
clusterName: String,
clusterPort : Int,
clusterAddress : String,
hostname: String,
listeningPort: Int)
object Validator {
def getHuffConfig(config: Config) : ValidatedNel[ConfigError, HuffConfig] =
Apply[ValidatedNel[ConfigError, ?]].map5(
config.parse[String] ("DL_CLUSTER_NAME").toValidatedNel,
config.parse[Int] ("DL_CLUSTER_PORT").toValidatedNel,
config.parse[String] ("DL_CLUSTER_ADDRESS").toValidatedNel,
config.parse[String] ("DL_HTTP_ADDRESS").toValidatedNel,
config.parse[Int] ("DL_HTTP_PORT").toValidatedNel) {
case (clusterName, clusterPort, clusterAddress, httpAddr, httpPort) =>
HuffConfig(clusterName, clusterPort, clusterAddress, httpAddr, httpPort)
}
}
Define types to represent “errors"
package xxx.config
import scala.concurrent.duration.{Duration,FiniteDuration}
import cats._
import cats.data._
import cats.implicits._
import cats.data.Validated
import cats.data.Validated.{Invalid, Valid}
// code that needs to remain hidden
sealed abstract class ConfigError
final case class MissingConfig(field : String) extends ConfigError
final case class ParseError(field: String) extends ConfigError
case class Config(map : Map[String,String])
case class HuffConfig(
clusterName: String,
clusterPort : Int,
clusterAddress : String,
hostname: String,
listeningPort: Int)
object Validator {
def getHuffConfig(config: Config) : ValidatedNel[ConfigError, HuffConfig] =
Apply[ValidatedNel[ConfigError, ?]].map5(
config.parse[String] ("DL_CLUSTER_NAME").toValidatedNel,
config.parse[Int] ("DL_CLUSTER_PORT").toValidatedNel,
config.parse[String] ("DL_CLUSTER_ADDRESS").toValidatedNel,
config.parse[String] ("DL_HTTP_ADDRESS").toValidatedNel,
config.parse[Int] ("DL_HTTP_PORT").toValidatedNel) {
case (clusterName, clusterPort, clusterAddress, httpAddr, httpPort) =>
HuffConfig(clusterName, clusterPort, clusterAddress, httpAddr, httpPort)
}
}
Define types to represent “errors"
Validate and read into configuration object.
package xxx.config
import scala.concurrent.duration.{Duration,FiniteDuration}
import cats._
import cats.data._
import cats.implicits._
import cats.data.Validated
import cats.data.Validated.{Invalid, Valid}
// code that needs to remain hidden
sealed abstract class ConfigError
final case class MissingConfig(field : String) extends ConfigError
final case class ParseError(field: String) extends ConfigError
case class Config(map : Map[String,String])
case class HuffConfig(
clusterName: String,
clusterPort : Int,
clusterAddress : String,
hostname: String,
listeningPort: Int)
object Validator {
def getHuffConfig(config: Config) : ValidatedNel[ConfigError, HuffConfig] =
Apply[ValidatedNel[ConfigError, ?]].map5(
config.parse[String] ("DL_CLUSTER_NAME").toValidatedNel,
config.parse[Int] ("DL_CLUSTER_PORT").toValidatedNel,
config.parse[String] ("DL_CLUSTER_ADDRESS").toValidatedNel,
config.parse[String] ("DL_HTTP_ADDRESS").toValidatedNel,
config.parse[Int] ("DL_HTTP_PORT").toValidatedNel) {
case (clusterName, clusterPort, clusterAddress, httpAddr, httpPort) =>
HuffConfig(clusterName, clusterPort, clusterAddress, httpAddr, httpPort)
}
}
Define types to represent “errors"
Validate and read into configuration object.
Validation logic
How does anyone create a stack of Monads ?
Monad Transformers
How does anyone create a stack of Monads ?
Monad Transformers
Let’s take a closer look
scala> case class Cat(name: String, alive: Boolean)
defined class Cat
scala> def isAlive = Reader{ (u:User) => if (u.alive) u.asRight[Throwable].toOption:: Nil
| else scala.util.Try(throw new Exception("Dead!")).asLeft[User].toOption::Nil }
isAlive2: cats.data.Reader[User,List[Option[User]]]
scala> def lookup = Cat("cat", true).some::Nil
lookup: List[Option[Cat]]
scala> for {
| someCat <- lookup
| } yield {
| for {
| cat <- someCat
| } yield isAlive(cat)
|}
res47: List[Option[cats.Id[List[Option[Cat]]]]] = List(Some(List(User(cat,true))))
Let’s say we like to look up a cat and find out whether its alive.
We would use Option[Cat] to say whether we can find one, and perhaps
Either[Throwable,Cat] to represent when cat is dead, we throw an exception
else we return the Cat
First Attempt
Let’s take a closer look
scala> case class Cat(name: String, alive: Boolean)
defined class Cat
scala> def isAlive =
| Reader{ (u: Cat) => if (u.alive) OptionT( u.asRight[Throwable].toOption:: Nil)
| else OptionT( scala.util.Try(throw new Exception("Dead!")).asLeft[Cat].toOption::Nil) }
isAlive: cats.data.Reader[Cat,cats.data.OptionT[List, Cat]]
scala> def lookup = OptionT(Cat("cat", true).some::Nil)
lookup: cats.data.OptionT[List, Cat]
scala> for {
| cat <- lookup
| checked <- isAlive(cat)
| } yield checked
res32: cats.data.OptionT[List, Cat] = OptionT(List(Some(Cat(cat,true))))
The nested-yield loops can quickly get very confusing ….
that’s where Monad Transformers help!
Second Attempt
Effectful Monads aka Eff-Monads
Effectful Monads

An alternative to Monad Transformers
http://atnos-org.github.io/eff/
Use-case #4
Putting in the type-definitions: making use of the

Reader, Writer, Either Effects from Eff !
import xxx.workflow.models.{WorkflowDescriptor, Service}
import scala.language.{postfixOps, higherKinds}
import org.atnos.eff._, all._, syntax.all._
import com.typesafe.config._
import com.typesafe.scalalogging._
class LoadWorkflowDescriptorEff {
import cats._, data._, implicits._
import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._
lazy val config = ConfigFactory.load()
lazy val logger = Logger(getClass)
type WorkflowIdReader[A] = Reader[String, A]
type WriterString[A] = Writer[String,A]
type DecodeFailure[A] = io.circe.DecodingFailure Either A
type ParseFailure[A] = io.circe.ParsingFailure Either A
// ...
}
import java.time._
type LoadDescStack =
Fx.fx6[WorkflowIdReader, WriterString, DecodeFailure, ParseFailure, Throwable Either ?, Eval]
def loadDescriptor : Eff[LoadDescStack, WorkflowDescriptor] =
for {
workflowId <- ask[LoadDescStack,String]
_ <- tell[LoadDescStack,String](s"[${Instant.now()}] About to load data about workflow: $workflowId")
contents <- fromEither[LoadDescStack,java.lang.Throwable,String](loadContents(workflowId))
_ <- tell[LoadDescStack,String](s"[${Instant.now()}] Data is loaded from storage: $contents")
json <- fromEither[LoadDescStack,io.circe.ParsingFailure,io.circe.Json](parse(contents))
_ <- tell[LoadDescStack, String](s"[${Instant.now()}] Workflow descriptor parsed successfully")
desc <- fromEither[LoadDescStack, io.circe.DecodingFailure, WorkflowDescriptor](json.as[WorkflowDescriptor])
_ <- tell[LoadDescStack, String](s"[${Instant.now()}] Workflow descriptor hydrated into object.")
} yield desc
// Below is a test and you can choose either runEval or attemptEval
// attemptEval is a better option as it captures any errors met during the
// computation.
//println(loadDescriptor.runReader("1").runWriter.runEither.runEither.runEither.runPure)
lazy val result = {
val a = loadDescriptor.runReader("1").runWriter.runEither.runEither.runEither.runPure
val t = a.get
t.joinRight
}
// the logging version
lazy val result2 = {
val a = loadDescriptor.runReader("1").runWriterLog.runEither.runEither.runEither.runPure
val t = a.get
t.joinRight
}
}
Use-case #4
import java.time._
type LoadDescStack =
Fx.fx6[WorkflowIdReader, WriterString, DecodeFailure, ParseFailure, Throwable Either ?, Eval]
def loadDescriptor : Eff[LoadDescStack, WorkflowDescriptor] =
for {
workflowId <- ask[LoadDescStack,String]
_ <- tell[LoadDescStack,String](s"[${Instant.now()}] About to load data about workflow: $workflowId")
contents <- fromEither[LoadDescStack,java.lang.Throwable,String](loadContents(workflowId))
_ <- tell[LoadDescStack,String](s"[${Instant.now()}] Data is loaded from storage: $contents")
json <- fromEither[LoadDescStack,io.circe.ParsingFailure,io.circe.Json](parse(contents))
_ <- tell[LoadDescStack, String](s"[${Instant.now()}] Workflow descriptor parsed successfully")
desc <- fromEither[LoadDescStack, io.circe.DecodingFailure, WorkflowDescriptor](json.as[WorkflowDescriptor])
_ <- tell[LoadDescStack, String](s"[${Instant.now()}] Workflow descriptor hydrated into object.")
} yield desc
// Below is a test and you can choose either runEval or attemptEval
// attemptEval is a better option as it captures any errors met during the
// computation.
//println(loadDescriptor.runReader("1").runWriter.runEither.runEither.runEither.runPure)
lazy val result = {
val a = loadDescriptor.runReader("1").runWriter.runEither.runEither.runEither.runPure
val t = a.get
t.joinRight
}
// the logging version
lazy val result2 = {
val a = loadDescriptor.runReader("1").runWriterLog.runEither.runEither.runEither.runPure
val t = a.get
t.joinRight
}
}
Use-case #4
Eff-Monads allows us to stack computations
Learning resources
https://www.haskell.org/tutorial/monads.html
http://eed3si9n.com/herding-cats/
http://typelevel.org/cats/
http://blog.higher-order.com/
https://gitter.im/typelevel/cats
That’s it from me :)
Questions ?

More Related Content

PDF
JAVA PROGRAMMING - The Collections Framework
PPT
Collections Framework
PDF
Scala collections api expressivity and brevity upgrade from java
PPTX
Scala for curious
PDF
Scala collections
ODP
Data structures in scala
PPTX
Scala Back to Basics: Type Classes
PDF
Data Structures In Scala
JAVA PROGRAMMING - The Collections Framework
Collections Framework
Scala collections api expressivity and brevity upgrade from java
Scala for curious
Scala collections
Data structures in scala
Scala Back to Basics: Type Classes
Data Structures In Scala

What's hot (20)

PDF
Python programming : Arrays
ODP
Introducing scala
PDF
Getting Started With Scala
PDF
An Introduction to Programming in Java: Arrays
PPTX
Java arrays
PDF
Java Arrays
PDF
Arrays in python
PDF
An Introduction to Part of C++ STL
PDF
Arrays In Python | Python Array Operations | Edureka
PPT
Lec 25 - arrays-strings
PDF
LectureNotes-03-DSA
PDF
Ti1220 Lecture 7: Polymorphism
PPT
Array in Java
PPTX
Array lecture
PPT
Java collections concept
PDF
Java chapter 6 - Arrays -syntax and use
PPTX
Arrays in Java
PPTX
PPTX
Python array
PDF
Arrays in python
Python programming : Arrays
Introducing scala
Getting Started With Scala
An Introduction to Programming in Java: Arrays
Java arrays
Java Arrays
Arrays in python
An Introduction to Part of C++ STL
Arrays In Python | Python Array Operations | Edureka
Lec 25 - arrays-strings
LectureNotes-03-DSA
Ti1220 Lecture 7: Polymorphism
Array in Java
Array lecture
Java collections concept
Java chapter 6 - Arrays -syntax and use
Arrays in Java
Python array
Arrays in python
Ad

Similar to Practical cats (20)

PDF
Introducing Monads and State Monad at PSUG
PPT
Thesis PPT
PPT
Thesis
PDF
Monads - Dublin Scala meetup
PDF
Monads and Monoids by Oleksiy Dyagilev
PPTX
Monads and friends demystified
PDF
Oh, All the things you'll traverse
PDF
Scala. Introduction to FP. Monads
PDF
Frp2016 3
PPTX
Monadic Computations in C++14
PDF
Scala or functional programming from a python developer's perspective
PDF
Introduction à Scala - Michel Schinz - January 2010
PDF
Mining Functional Patterns
PDF
Functions, Types, Programs and Effects
PDF
Demystifying functional programming with Scala
PDF
Big picture of category theory in scala with deep dive into contravariant and...
PDF
Big picture of category theory in scala with deep dive into contravariant and...
PDF
Introduction to parallel and distributed computation with spark
PDF
It's All About Morphisms
PDF
Why functional programming and category theory strongly matters - Piotr Parad...
Introducing Monads and State Monad at PSUG
Thesis PPT
Thesis
Monads - Dublin Scala meetup
Monads and Monoids by Oleksiy Dyagilev
Monads and friends demystified
Oh, All the things you'll traverse
Scala. Introduction to FP. Monads
Frp2016 3
Monadic Computations in C++14
Scala or functional programming from a python developer's perspective
Introduction à Scala - Michel Schinz - January 2010
Mining Functional Patterns
Functions, Types, Programs and Effects
Demystifying functional programming with Scala
Big picture of category theory in scala with deep dive into contravariant and...
Big picture of category theory in scala with deep dive into contravariant and...
Introduction to parallel and distributed computation with spark
It's All About Morphisms
Why functional programming and category theory strongly matters - Piotr Parad...
Ad

More from Raymond Tay (8)

PDF
Principled io in_scala_2019_distribution
PDF
Building a modern data platform with scala, akka, apache beam
PDF
Toying with spark
PDF
Distributed computing for new bloods
PPT
Functional programming with_scala
PDF
Introduction to cuda geek camp singapore 2011
PPTX
Introduction to Erlang
PDF
Introduction to CUDA
Principled io in_scala_2019_distribution
Building a modern data platform with scala, akka, apache beam
Toying with spark
Distributed computing for new bloods
Functional programming with_scala
Introduction to cuda geek camp singapore 2011
Introduction to Erlang
Introduction to CUDA

Recently uploaded (20)

PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PDF
cuic standard and advanced reporting.pdf
PDF
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PPTX
Group 1 Presentation -Planning and Decision Making .pptx
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PPTX
A Presentation on Artificial Intelligence
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
A comparative analysis of optical character recognition models for extracting...
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PPTX
Programs and apps: productivity, graphics, security and other tools
PDF
Encapsulation theory and applications.pdf
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PPTX
1. Introduction to Computer Programming.pptx
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
Spectral efficient network and resource selection model in 5G networks
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
Building Integrated photovoltaic BIPV_UPV.pdf
MIND Revenue Release Quarter 2 2025 Press Release
cuic standard and advanced reporting.pdf
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
Reach Out and Touch Someone: Haptics and Empathic Computing
Group 1 Presentation -Planning and Decision Making .pptx
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
“AI and Expert System Decision Support & Business Intelligence Systems”
A Presentation on Artificial Intelligence
Advanced methodologies resolving dimensionality complications for autism neur...
A comparative analysis of optical character recognition models for extracting...
The Rise and Fall of 3GPP – Time for a Sabbatical?
Programs and apps: productivity, graphics, security and other tools
Encapsulation theory and applications.pdf
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
1. Introduction to Computer Programming.pptx
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
Spectral efficient network and resource selection model in 5G networks
Mobile App Security Testing_ A Comprehensive Guide.pdf

Practical cats

  • 4. CATS…. To use cats effectively, understand what each construct does: Functor Monads Applicatives Monoids Semigroups SO MANY ACRONYMS !!! Validated
  • 6. Building blocks … Understand that they are building blocks so that you can write code that is pure and code that has side-effects — separation of concerns.
  • 7. Typeclasses … Each of the type class (e.g. functors, monoids, monads etc) are governed by laws. Typeclasses! they are behaviours that can be “inherited” by your code.
  • 8. Semigroups - what are they? trait Semigroup[A] { def combine(x: A, y: A) : A } general structure to define things that can be combined. *Cats provides “default” implementations; developers (like you & me) need to provide implementations that conform to the traits. *
  • 9. Monoids - what are they? trait Monoid[A] extends Semigroup[A] { def empty: A def combine(x: A, y: A) : A } general structure to define things that can be combined and has a “default” element. *Cats provides “default” implementations; developers (like you & me) need to provide implementations that conform to the traits. *
  • 10. Monoids - what are they? > import cats._, data._, implicits._ > Monoid[String].combine(“hi”, “there”) // res0: String = “hithere” > “hi” |+| “there” // res1: String = “hithere”
  • 11. Use case for Monoids/Semigroups They’re good for combining 2 or more things of a similar nature data-type-a data-type-b data-stream end- point parser collector of either data-type-a or data-type-b
  • 12. Use case #1 - Monoids for “smashing” values * all names used here do not reflect the actuals * // Monoid[DataTypeAB] defined somewhere else def buildDataFromStream(datatypeA : DataTypeA, datatypeB : DataTypeB, accumulator: DatatypeAB) = validateData(datatypeA, datatypeB).fold( onError => { // `orError` is lifted into the datatype val errors = Monoid[DatatypeAB].empty.copy(lift(onError)) Monoid[DatatypeAB].combine(accumulator, errors) }, parsedValue => { // `parsedValue` is lifted into the datatype val newValue = Monoid[DatatypeAB].empty.copy(lift(parsedValue)) Monoid[DatatypeAB].combine(accumulator, newValue) } )
  • 13. Functors - what are they? trait Functor[F[_]] { def map[A,B](fa: F[A])(f: A => B) : F[B] } general structure to represent something that can be mapped over. If you’ve been using Lists , Options, Eithers, Futures in Scala, you’ve been using functors. !!! They are very common structures indeed ☺ !!! * functors are used in clever things like recursion-schemes *
  • 14. Functors - what are they? > import cats._, data._, implicits._ > Functor[List].lift((x:Int) => x + 1) // res0: List[Int] => List[Int] > res0(List(1)) // res1: List[Int] = List(2) * Nugget of info: Functors preserve “structure” *
  • 15. Monads Monads are meant for sequencing computations
  • 16. Monads someList.flatmap(element => someOtherList.flatmap(element2 => (element, element2))) *No tuples generated if either “someList” Or “someOtherList” is empty*
  • 17. Monads someList.flatmap(element => someOtherList.flatmap(element2 => (element, element2))) Monads allows for short-circuiting of computations
  • 18. Monads - a quick summary? Writers - information can be carried along with the computation Readers - compose operations that depend on some input. State - allows state to be “propagated” Eval - abstracts over eager vs lazy evaluation.
  • 19. Monads - examples > List(1,2,3) >>= (value => List(value+1)) > res0: List[Int] = List(2,3,4) def >>=[A,B](fa: F[A])(f:A => F[B]): F[B] = flatMap(fa)(f) “>>=“ is also known as “bind” (in Cats, its really “flatMap”)
  • 20. Monads - examples > Monad[List].lift((x:Int) => x + 1)(List(1,2,3)) > res1: List[Int] = List(2,3,4) Typeclasses allows you to define re-usable code by lifting functions.
  • 21. Monads - examples > Monad[List].pure(4) > res2: List[Int] = List(4) This is to lift values into a context, in this case Monadic context.
  • 22. Monads - flow scala> def first = Reader( (x:Int) => Monad[List].ifM(List(true,true))(x::Nil, Nil) ) extractGroups: cats.data.Reader[Int,List[Int]] scala> def second = Reader( (x:Int) => Monad[List].ifM(List(true,true))(x::Nil, Nil) ) loadGroup: cats.data.Reader[Int,List[Int]] scala> for { g <- first(4); gg <- second(g) } yield gg res21: List[Int] = List(4, 4, 4, 4)
  • 23. Writer Monad “Writers” are typically used to carry not only the value of a computation but also some other information (typically, its used to carry logging info). source: http://eed3si9n.com/herding-cats/Writer.html
  • 24. Writer Monad scala> def logNumber(x: Int): Writer[List[String], Int] =          Writer(List("Got number: " + x.show), 3) logNumber: (x: Int)cats.data.Writer[List[String],Int] scala> def multWithLog: Writer[List[String], Int] =          for {            a <- logNumber(3)            b <- logNumber(5)          } yield a * b multWithLog: cats.data.Writer[List[String],Int] scala> multWithLog.run res2: cats.Id[(List[String], Int)] = (List(Got number: 3, Got number: 5),9) scala> multWithLog.reset res6: cats.data.WriterT[cats.Id,List[String],Int] = WriterT((List(),9)) scala> multWithLog.swap res8: cats.data.WriterT[cats.Id,Int,List[String]] = WriterT((9,List(Got number: 4, Got number: 3))) scala> multWithLog.value res9: cats.Id[Int] = 9 scala> multWithLog.written res10: cats.Id[List[String]] = List(Got number: 4, Got number: 3) source: http://eed3si9n.com/herding-cats/Writer.html Compose Writers
  • 25. Reader Monad “Readers” allows us to compose operations which depend on some input. source: http://eed3si9n.com/herding-cats/Reader.html
  • 26. Reader Monad case class Config(setting: String, value: String) def getSetting = Reader { (config: Config) => config.setting } def getValue = Reader { (config: Config) => config.value } for { s <- getSetting v <- getValue } yield Config(s, v) Compose Readers FP-style to abstract and encapsulate.
  • 27. State Monad Allows us to pass state-information around in a computation. http://eed3si9n.com/herding-cats/State.html
  • 28. Use case #3 - Reader + State Monad def process: Reader[Elem, Seq[Mapping]] = Reader { (xml: Elem) => for { groups <- extractGroups(dataXml).toOption group <- groups grpCfg <- loadGroupConfig(group).toOption stateObj <- ConfigState(grpCfg).pure[Option] records <- loadRecords(group).toOption record <- records row <- processRecord(i)(stateObj)(record).pure[Option] } yield { // processing … } } case class ConfigState(init: Config) { private[this] var currentState: Config = init def storeCfg : State[Config, Config] = State{ (cfg: Config) => val prevState = currentState currentState = cfg (currentState, prevState) } def loadCfg : Config = ( for { s <- State.get[Config] } yield s ).runA(currentState).value }
  • 29. Use case #3 - Reader + State Monad def process: Reader[Elem, Seq[Mapping]] = Reader { (xml: Elem) => for { groups <- extractGroups(dataXml).toOption group <- groups grpCfg <- loadGroupConfig(group).toOption stateObj <- ConfigState(grpCfg).pure[Option] records <- loadRecords(group).toOption record <- records row <- processRecord(i)(stateObj)(record).pure[Option] } yield { // processing … } } case class ConfigState(init: Config) { private[this] var currentState: Config = init def storeCfg : State[Config, Config] = State{ (cfg: Config) => val prevState = currentState currentState = cfg (currentState, prevState) } def loadCfg : Config = ( for { s <- State.get[Config] } yield s ).runA(currentState).value } Separation of concerns State management
  • 30. Applicative Applicatives allows for functions to be lifted over a structure (Functor). Because the function and the value it’s being applied to both have structures, hence its needs to be combined.
  • 31. Applicative - examples scala> Applicative[List].lift((x:Int) => x + 1) res1: List[Int] => List[Int] = <function1> scala> Applicative[List].lift( | (x:List[Int=>Int]) => | x.map(f => f(2))) | (List( List((x:Int) => x + 1 ))) res7: List[List[Int]] = List(List(3)) scala> val fs = List(List((x:Int) => x + 1)) fs: List[List[Int => Int]] = List(List(<function1>)) scala> fs.map(_(2)) res15: cats.data.Nested[List,List,Int] = Nested(List(List(3))) Applicative is like a Functor
  • 32. Applicative - examples scala> Applicative[List].lift((x:Int) => x + 1) res1: List[Int] => List[Int] = <function1> scala> Applicative[List].lift( | (x:List[Int=>Int]) => | x.map(f => f(2))) | (List( List((x:Int) => x + 1 ))) res7: List[List[Int]] = List(List(3)) scala> val fs = List(List((x:Int) => x + 1)) fs: List[List[Int => Int]] = List(List(<function1>)) scala> fs.map(_(2)) res15: cats.data.Nested[List,List,Int] = Nested(List(List(3))) Applicative is like a Functor Applying a function which is nested.
  • 33. Applicative - examples scala> Applicative[List].lift((x:Int) => x + 1) res1: List[Int] => List[Int] = <function1> scala> Applicative[List].lift( | (x:List[Int=>Int]) => | x.map(f => f(2))) | (List( List((x:Int) => x + 1 ))) res7: List[List[Int]] = List(List(3)) scala> val fs = List(List((x:Int) => x + 1)) fs: List[List[Int => Int]] = List(List(<function1>)) scala> fs.map(_(2)) res15: cats.data.Nested[List,List,Int] = Nested(List(List(3))) Applicative is like a Functor Applying a function which is nested. Cat has a “Nested” to achieve the same.
  • 34. Applicative - examples A typical application is to leverage Applicatives in writing Logic to validate configurations, forms etc
  • 35. import cats.Cartesian import cats.data.Validated import cats.instances.list._ // Semigroup for List type AllErrorsOr[A] = Validated[List[String], A] Cartesian[AllErrorsOr].product( Validated.invalid(List("Error 1")), Validated.invalid(List("Error 2")) ) // res1: AllErrorsOr[(Nothing, Nothing)] = Invalid(List(Error 1,Error 2)) Applicative - examples
  • 36. package xxx.config import scala.concurrent.duration.{Duration,FiniteDuration} import cats._ import cats.data._ import cats.implicits._ import cats.data.Validated import cats.data.Validated.{Invalid, Valid} // code that needs to remain hidden sealed abstract class ConfigError final case class MissingConfig(field : String) extends ConfigError final case class ParseError(field: String) extends ConfigError case class Config(map : Map[String,String]) case class HuffConfig( clusterName: String, clusterPort : Int, clusterAddress : String, hostname: String, listeningPort: Int) object Validator { def getHuffConfig(config: Config) : ValidatedNel[ConfigError, HuffConfig] = Apply[ValidatedNel[ConfigError, ?]].map5( config.parse[String] ("DL_CLUSTER_NAME").toValidatedNel, config.parse[Int] ("DL_CLUSTER_PORT").toValidatedNel, config.parse[String] ("DL_CLUSTER_ADDRESS").toValidatedNel, config.parse[String] ("DL_HTTP_ADDRESS").toValidatedNel, config.parse[Int] ("DL_HTTP_PORT").toValidatedNel) { case (clusterName, clusterPort, clusterAddress, httpAddr, httpPort) => HuffConfig(clusterName, clusterPort, clusterAddress, httpAddr, httpPort) } }
  • 37. package xxx.config import scala.concurrent.duration.{Duration,FiniteDuration} import cats._ import cats.data._ import cats.implicits._ import cats.data.Validated import cats.data.Validated.{Invalid, Valid} // code that needs to remain hidden sealed abstract class ConfigError final case class MissingConfig(field : String) extends ConfigError final case class ParseError(field: String) extends ConfigError case class Config(map : Map[String,String]) case class HuffConfig( clusterName: String, clusterPort : Int, clusterAddress : String, hostname: String, listeningPort: Int) object Validator { def getHuffConfig(config: Config) : ValidatedNel[ConfigError, HuffConfig] = Apply[ValidatedNel[ConfigError, ?]].map5( config.parse[String] ("DL_CLUSTER_NAME").toValidatedNel, config.parse[Int] ("DL_CLUSTER_PORT").toValidatedNel, config.parse[String] ("DL_CLUSTER_ADDRESS").toValidatedNel, config.parse[String] ("DL_HTTP_ADDRESS").toValidatedNel, config.parse[Int] ("DL_HTTP_PORT").toValidatedNel) { case (clusterName, clusterPort, clusterAddress, httpAddr, httpPort) => HuffConfig(clusterName, clusterPort, clusterAddress, httpAddr, httpPort) } } Define types to represent “errors"
  • 38. package xxx.config import scala.concurrent.duration.{Duration,FiniteDuration} import cats._ import cats.data._ import cats.implicits._ import cats.data.Validated import cats.data.Validated.{Invalid, Valid} // code that needs to remain hidden sealed abstract class ConfigError final case class MissingConfig(field : String) extends ConfigError final case class ParseError(field: String) extends ConfigError case class Config(map : Map[String,String]) case class HuffConfig( clusterName: String, clusterPort : Int, clusterAddress : String, hostname: String, listeningPort: Int) object Validator { def getHuffConfig(config: Config) : ValidatedNel[ConfigError, HuffConfig] = Apply[ValidatedNel[ConfigError, ?]].map5( config.parse[String] ("DL_CLUSTER_NAME").toValidatedNel, config.parse[Int] ("DL_CLUSTER_PORT").toValidatedNel, config.parse[String] ("DL_CLUSTER_ADDRESS").toValidatedNel, config.parse[String] ("DL_HTTP_ADDRESS").toValidatedNel, config.parse[Int] ("DL_HTTP_PORT").toValidatedNel) { case (clusterName, clusterPort, clusterAddress, httpAddr, httpPort) => HuffConfig(clusterName, clusterPort, clusterAddress, httpAddr, httpPort) } } Define types to represent “errors" Validate and read into configuration object.
  • 39. package xxx.config import scala.concurrent.duration.{Duration,FiniteDuration} import cats._ import cats.data._ import cats.implicits._ import cats.data.Validated import cats.data.Validated.{Invalid, Valid} // code that needs to remain hidden sealed abstract class ConfigError final case class MissingConfig(field : String) extends ConfigError final case class ParseError(field: String) extends ConfigError case class Config(map : Map[String,String]) case class HuffConfig( clusterName: String, clusterPort : Int, clusterAddress : String, hostname: String, listeningPort: Int) object Validator { def getHuffConfig(config: Config) : ValidatedNel[ConfigError, HuffConfig] = Apply[ValidatedNel[ConfigError, ?]].map5( config.parse[String] ("DL_CLUSTER_NAME").toValidatedNel, config.parse[Int] ("DL_CLUSTER_PORT").toValidatedNel, config.parse[String] ("DL_CLUSTER_ADDRESS").toValidatedNel, config.parse[String] ("DL_HTTP_ADDRESS").toValidatedNel, config.parse[Int] ("DL_HTTP_PORT").toValidatedNel) { case (clusterName, clusterPort, clusterAddress, httpAddr, httpPort) => HuffConfig(clusterName, clusterPort, clusterAddress, httpAddr, httpPort) } } Define types to represent “errors" Validate and read into configuration object. Validation logic
  • 40. How does anyone create a stack of Monads ? Monad Transformers
  • 41. How does anyone create a stack of Monads ? Monad Transformers
  • 42. Let’s take a closer look scala> case class Cat(name: String, alive: Boolean) defined class Cat scala> def isAlive = Reader{ (u:User) => if (u.alive) u.asRight[Throwable].toOption:: Nil | else scala.util.Try(throw new Exception("Dead!")).asLeft[User].toOption::Nil } isAlive2: cats.data.Reader[User,List[Option[User]]] scala> def lookup = Cat("cat", true).some::Nil lookup: List[Option[Cat]] scala> for { | someCat <- lookup | } yield { | for { | cat <- someCat | } yield isAlive(cat) |} res47: List[Option[cats.Id[List[Option[Cat]]]]] = List(Some(List(User(cat,true)))) Let’s say we like to look up a cat and find out whether its alive. We would use Option[Cat] to say whether we can find one, and perhaps Either[Throwable,Cat] to represent when cat is dead, we throw an exception else we return the Cat First Attempt
  • 43. Let’s take a closer look scala> case class Cat(name: String, alive: Boolean) defined class Cat scala> def isAlive = | Reader{ (u: Cat) => if (u.alive) OptionT( u.asRight[Throwable].toOption:: Nil) | else OptionT( scala.util.Try(throw new Exception("Dead!")).asLeft[Cat].toOption::Nil) } isAlive: cats.data.Reader[Cat,cats.data.OptionT[List, Cat]] scala> def lookup = OptionT(Cat("cat", true).some::Nil) lookup: cats.data.OptionT[List, Cat] scala> for { | cat <- lookup | checked <- isAlive(cat) | } yield checked res32: cats.data.OptionT[List, Cat] = OptionT(List(Some(Cat(cat,true)))) The nested-yield loops can quickly get very confusing …. that’s where Monad Transformers help! Second Attempt
  • 44. Effectful Monads aka Eff-Monads Effectful Monads An alternative to Monad Transformers http://atnos-org.github.io/eff/
  • 45. Use-case #4 Putting in the type-definitions: making use of the Reader, Writer, Either Effects from Eff ! import xxx.workflow.models.{WorkflowDescriptor, Service} import scala.language.{postfixOps, higherKinds} import org.atnos.eff._, all._, syntax.all._ import com.typesafe.config._ import com.typesafe.scalalogging._ class LoadWorkflowDescriptorEff { import cats._, data._, implicits._ import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._ lazy val config = ConfigFactory.load() lazy val logger = Logger(getClass) type WorkflowIdReader[A] = Reader[String, A] type WriterString[A] = Writer[String,A] type DecodeFailure[A] = io.circe.DecodingFailure Either A type ParseFailure[A] = io.circe.ParsingFailure Either A // ... }
  • 46. import java.time._ type LoadDescStack = Fx.fx6[WorkflowIdReader, WriterString, DecodeFailure, ParseFailure, Throwable Either ?, Eval] def loadDescriptor : Eff[LoadDescStack, WorkflowDescriptor] = for { workflowId <- ask[LoadDescStack,String] _ <- tell[LoadDescStack,String](s"[${Instant.now()}] About to load data about workflow: $workflowId") contents <- fromEither[LoadDescStack,java.lang.Throwable,String](loadContents(workflowId)) _ <- tell[LoadDescStack,String](s"[${Instant.now()}] Data is loaded from storage: $contents") json <- fromEither[LoadDescStack,io.circe.ParsingFailure,io.circe.Json](parse(contents)) _ <- tell[LoadDescStack, String](s"[${Instant.now()}] Workflow descriptor parsed successfully") desc <- fromEither[LoadDescStack, io.circe.DecodingFailure, WorkflowDescriptor](json.as[WorkflowDescriptor]) _ <- tell[LoadDescStack, String](s"[${Instant.now()}] Workflow descriptor hydrated into object.") } yield desc // Below is a test and you can choose either runEval or attemptEval // attemptEval is a better option as it captures any errors met during the // computation. //println(loadDescriptor.runReader("1").runWriter.runEither.runEither.runEither.runPure) lazy val result = { val a = loadDescriptor.runReader("1").runWriter.runEither.runEither.runEither.runPure val t = a.get t.joinRight } // the logging version lazy val result2 = { val a = loadDescriptor.runReader("1").runWriterLog.runEither.runEither.runEither.runPure val t = a.get t.joinRight } } Use-case #4
  • 47. import java.time._ type LoadDescStack = Fx.fx6[WorkflowIdReader, WriterString, DecodeFailure, ParseFailure, Throwable Either ?, Eval] def loadDescriptor : Eff[LoadDescStack, WorkflowDescriptor] = for { workflowId <- ask[LoadDescStack,String] _ <- tell[LoadDescStack,String](s"[${Instant.now()}] About to load data about workflow: $workflowId") contents <- fromEither[LoadDescStack,java.lang.Throwable,String](loadContents(workflowId)) _ <- tell[LoadDescStack,String](s"[${Instant.now()}] Data is loaded from storage: $contents") json <- fromEither[LoadDescStack,io.circe.ParsingFailure,io.circe.Json](parse(contents)) _ <- tell[LoadDescStack, String](s"[${Instant.now()}] Workflow descriptor parsed successfully") desc <- fromEither[LoadDescStack, io.circe.DecodingFailure, WorkflowDescriptor](json.as[WorkflowDescriptor]) _ <- tell[LoadDescStack, String](s"[${Instant.now()}] Workflow descriptor hydrated into object.") } yield desc // Below is a test and you can choose either runEval or attemptEval // attemptEval is a better option as it captures any errors met during the // computation. //println(loadDescriptor.runReader("1").runWriter.runEither.runEither.runEither.runPure) lazy val result = { val a = loadDescriptor.runReader("1").runWriter.runEither.runEither.runEither.runPure val t = a.get t.joinRight } // the logging version lazy val result2 = { val a = loadDescriptor.runReader("1").runWriterLog.runEither.runEither.runEither.runPure val t = a.get t.joinRight } } Use-case #4 Eff-Monads allows us to stack computations
  • 49. That’s it from me :) Questions ?