SlideShare une entreprise Scribd logo
1  sur  88
Télécharger pour lire hors ligne
Implementing the IO Monad
© 2019 Hermann Hueck
https://github.com/hermannhueck/implementing-io-monad
1 / 87
Abstract
With my simple implementation I wanted to demonstrate the basic ideas of th
IO Monad.
My impl of the IO Monad is just a feasibility study, not production code!
When coding my impl of IO I was very much inspired by cats.effect.IO and
monix.eval.Task which I studied at that time. Both are implementions of the IO
Monad.
The API of my IO is very similar to the basics of Monix Task. This IO
implementation also helped me to understand the IO Monad (of cats-effect)
and Monix Task.
Interop with Future is also supported. You can convert IO to a Future. Vice
versa you can convert a Future to an IO.
The development of my impl can be followed step by step in the code files in
package iomonad.
2 / 87
Agenda
1. Referential Transparency
2. Is Future referentially transparent?
3. The IO Monad
4. Resources
3 / 87
1. Referential Transparency
4 / 87
Referential Transparency
An expression is called referentially transparent if it can be
replaced with its corresponding value without changing the
program's behavior.
This requires that the expression is purepure, that is to say the
expression value must be the same for the same inputs and its
evaluation must have no side effectsno side effects.
https://en.wikipedia.org/wiki/Referential_transparency
5 / 87
Referential Transparency Benefits
(Equational) Reasoning about code
Refactoring is easier
Testing is easier
Separate pure code from impure code
Potential compiler optimizations (more in Haskell than in Scala)
(e.g. memoization, parallelisation, compute expressions at compile time)
"What Referential Transparency can do for you"
Talk by Luka Jacobowitz at ScalaIO 2017
https://www.youtube.com/watch?v=X-cEGEJMx_4
6 / 87
func is not referentially transparent!
def func(ioa1: Unit, ioa2: Unit): Unit = {
ioa1
ioa2
}
func(println("hi"), println("hi")) // prints "hi" twice
//=> hi
//=> hi
println("-----")
val x: Unit = println("hi")
func(x, x) // prints "hi" once
//=> hi
7 / 87
func is referentially transparent!
def putStrLn(line: String): IO[Unit] =
IO.eval { println(line) }
def func(ioa1: IO[Unit], ioa2: IO[Unit]): IO[Unit] =
for {
_ <- ioa1
_ <- ioa2
} yield ()
func(putStrLn("hi"), putStrLn("hi")).run() // prints "hi" twice
//=> hi
//=> hi
println("-----")
val x: IO[Unit] = putStrLn("hi")
func(x, x).run() // prints "hi" twice
//=> hi
//=> hi
8 / 87
2. Is Future referentially
transparent?
9 / 87
Is Future referentially transparent?
val future1: Future[(Int, Int)] = {
val atomicInt = new AtomicInteger(0)
val future: Future[Int] = Future { atomicInt.incrementAndGet }
for {
x <- future
y <- future
} yield (x, y)
}
future1 onComplete println // Success((1,1))
// same as future1, but inlined
val future2: Future[(Int, Int)] = {
val atomicInt = new AtomicInteger(0)
for {
x <- Future { atomicInt.incrementAndGet }
y <- Future { atomicInt.incrementAndGet }
} yield (x, y)
}
future2 onComplete println // Success((1,2)) <-- not the same result
10 / 87
Is Future referentially transparent?
val future1: Future[(Int, Int)] = {
val atomicInt = new AtomicInteger(0)
val future: Future[Int] = Future { atomicInt.incrementAndGet }
for {
x <- future
y <- future
} yield (x, y)
}
future1 onComplete println // Success((1,1))
// same as future1, but inlined
val future2: Future[(Int, Int)] = {
val atomicInt = new AtomicInteger(0)
for {
x <- Future { atomicInt.incrementAndGet }
y <- Future { atomicInt.incrementAndGet }
} yield (x, y)
}
future2 onComplete println // Success((1,2)) <-- not the same result
No! 11 / 87
Is Monix Task referentially transparent?
val task1: Task[(Int, Int)] = {
val atomicInt = new AtomicInteger(0)
val task: Task[Int] = Task { atomicInt.incrementAndGet }
for {
x <- task
y <- task
} yield (x, y)
}
task1 runAsync println // Success((1,2))
// same as task1, but inlined
val task2: Task[(Int, Int)] = {
val atomicInt = new AtomicInteger(0)
for {
x <- Task { atomicInt.incrementAndGet }
y <- Task { atomicInt.incrementAndGet }
} yield (x, y)
}
task2 runAsync println // Success((1,2)) <-- same result
12 / 87
Is Monix Task referentially transparent?
val task1: Task[(Int, Int)] = {
val atomicInt = new AtomicInteger(0)
val task: Task[Int] = Task { atomicInt.incrementAndGet }
for {
x <- task
y <- task
} yield (x, y)
}
task1 runAsync println // Success((1,2))
// same as task1, but inlined
val task2: Task[(Int, Int)] = {
val atomicInt = new AtomicInteger(0)
for {
x <- Task { atomicInt.incrementAndGet }
y <- Task { atomicInt.incrementAndGet }
} yield (x, y)
}
task2 runAsync println // Success((1,2)) <-- same result
Yes! 13 / 87
Is my IO Monad referentially transparent?
val io1: IO[(Int, Int)] = {
val atomicInt = new AtomicInteger(0)
val io: IO[Int] = IO { atomicInt.incrementAndGet }
for {
x <- io
y <- io
} yield (x, y)
}
io1.runToFuture onComplete println // Success((1,2))
// same as io1, but inlined
val io2: IO[(Int, Int)] = {
val atomicInt = new AtomicInteger(0)
for {
x <- IO { atomicInt.incrementAndGet }
y <- IO { atomicInt.incrementAndGet }
} yield (x, y)
}
io2.runToFuture onComplete println // Success((1,2)) <-- same result
14 / 87
Is my IO Monad referentially transparent?
val io1: IO[(Int, Int)] = {
val atomicInt = new AtomicInteger(0)
val io: IO[Int] = IO { atomicInt.incrementAndGet }
for {
x <- io
y <- io
} yield (x, y)
}
io1.runToFuture onComplete println // Success((1,2))
// same as io1, but inlined
val io2: IO[(Int, Int)] = {
val atomicInt = new AtomicInteger(0)
for {
x <- IO { atomicInt.incrementAndGet }
y <- IO { atomicInt.incrementAndGet }
} yield (x, y)
}
io2.runToFuture onComplete println // Success((1,2)) <-- same result
Yes! 15 / 87
3. The IO Monad
16 / 87
1. Impure IO Program with side e!ects
// impure program
def program(): Unit = {
print("Welcome to Scala! What's your name? ")
val name = scala.io.StdIn.readLine
println(s"Well hello, $name!")
}
17 / 87
1. Impure IO Program with side e!ects
// impure program
def program(): Unit = {
print("Welcome to Scala! What's your name? ")
val name = scala.io.StdIn.readLine
println(s"Well hello, $name!")
}
program()
18 / 87
1. Impure IO Program with side e!ects
// impure program
def program(): Unit = {
print("Welcome to Scala! What's your name? ")
val name = scala.io.StdIn.readLine
println(s"Well hello, $name!")
}
program()
Whenever a method or a function returns Unit it is impureimpure (or it is a
noop). It's intension is to produce a side effect.
A purepure function always returns a value of some type
(and doesn't produce a side effect inside).
19 / 87
2. Pure IO Program without side e!ects
// pure program
val program: () => Unit = // () => Unit is syntactic sugar for: Function0[Unit]
() => {
print("Welcome to Scala! What's your name? ")
val name = scala.io.StdIn.readLine
println(s"Well hello, $name!")
}
20 / 87
2. Pure IO Program without side e!ects
// pure program
val program: () => Unit = // () => Unit is syntactic sugar for: Function0[Unit]
() => {
print("Welcome to Scala! What's your name? ")
val name = scala.io.StdIn.readLine
println(s"Well hello, $name!")
}
program() // producing the side effects "at the end of the world"
21 / 87
2. Pure IO Program without side e!ects
// pure program
val program: () => Unit = // () => Unit is syntactic sugar for: Function0[Unit]
() => {
print("Welcome to Scala! What's your name? ")
val name = scala.io.StdIn.readLine
println(s"Well hello, $name!")
}
program() // producing the side effects "at the end of the world"
Make the program a function returning Unit: Function0[Unit]
Free of side effects in it's definition
Produces side effects only when run (at the end of the world)
program can be a val (if no parameters are passed to it)
22 / 87
3. Wrap Function0[A] in a case class
case class IO[A](run: () => A)
23 / 87
3. Wrap Function0[A] in a case class
case class IO[A](run: () => A)
// pure program
val program: IO[Unit] = IO {
() => {
print("Welcome to Scala! What's your name? ")
val name = scala.io.StdIn.readLine
println(s"Well hello, $name!")
}
}
24 / 87
3. Wrap Function0[A] in a case class
case class IO[A](run: () => A)
// pure program
val program: IO[Unit] = IO {
() => {
print("Welcome to Scala! What's your name? ")
val name = scala.io.StdIn.readLine
println(s"Well hello, $name!")
}
}
program.run() // producing the side effects "at the end of the world"
25 / 87
3. Wrap Function0[A] in a case class
case class IO[A](run: () => A)
// pure program
val program: IO[Unit] = IO {
() => {
print("Welcome to Scala! What's your name? ")
val name = scala.io.StdIn.readLine
println(s"Well hello, $name!")
}
}
program.run() // producing the side effects "at the end of the world"
IO[A] wraps a Function0[A] in a case class.
This is useful to implement further extensions on that case class.
26 / 87
4. IO#map and IO#flatMap
case class IO[A](run: () => A) {
def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() }
def map[B](f: A => B): IO[B] = IO { () => f(run()) }
def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a)
}
27 / 87
4. IO#map and IO#flatMap
case class IO[A](run: () => A) {
def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() }
def map[B](f: A => B): IO[B] = IO { () => f(run()) }
def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a)
}
val program: IO[Unit] = for {
_ <- IO { () => print(s"Welcome to Scala! What's your name? ") }
name <- IO { () => scala.io.StdIn.readLine }
_ <- IO { () => println(s"Well hello, $name!") }
} yield ()
28 / 87
4. IO#map and IO#flatMap
case class IO[A](run: () => A) {
def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() }
def map[B](f: A => B): IO[B] = IO { () => f(run()) }
def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a)
}
val program: IO[Unit] = for {
_ <- IO { () => print(s"Welcome to Scala! What's your name? ") }
name <- IO { () => scala.io.StdIn.readLine }
_ <- IO { () => println(s"Well hello, $name!") }
} yield ()
program.run() // producing the side effects "at the end of the world"
29 / 87
4. IO#map and IO#flatMap
case class IO[A](run: () => A) {
def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() }
def map[B](f: A => B): IO[B] = IO { () => f(run()) }
def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a)
}
val program: IO[Unit] = for {
_ <- IO { () => print(s"Welcome to Scala! What's your name? ") }
name <- IO { () => scala.io.StdIn.readLine }
_ <- IO { () => println(s"Well hello, $name!") }
} yield ()
program.run() // producing the side effects "at the end of the world"
With map and flatMap IO[A] is monadic (but it is not yet a Monad).
IO is ready for for-comprehensions.
This allows the composition of programs from smaller components.
30 / 87
5. Companion object IO with pure and eval
case class IO[A](run: () => A) {
def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() }
def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a)))
def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a)
}
object IO {
def pure[A](value: A): IO[A] = IO { () => value } // eager
def eval[A](thunk: => A): IO[A] = IO { () => thunk } // lazy
}
31 / 87
5. Companion object IO with pure and eval
case class IO[A](run: () => A) {
def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() }
def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a)))
def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a)
}
object IO {
def pure[A](value: A): IO[A] = IO { () => value } // eager
def eval[A](thunk: => A): IO[A] = IO { () => thunk } // lazy
}
val program: IO[Unit] = for {
welcome <- IO.pure("Welcome to Scala!")
_ <- IO.eval { print(s"$welcome What's your name? ") }
name <- IO.eval { scala.io.StdIn.readLine } // simpler with IO.eval
_ <- IO.eval { println(s"Well hello, $name!") }
} yield ()
32 / 87
5. Companion object IO with pure and eval
case class IO[A](run: () => A) {
def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() }
def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a)))
def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a)
}
object IO {
def pure[A](value: A): IO[A] = IO { () => value } // eager
def eval[A](thunk: => A): IO[A] = IO { () => thunk } // lazy
}
val program: IO[Unit] = for {
welcome <- IO.pure("Welcome to Scala!")
_ <- IO.eval { print(s"$welcome What's your name? ") }
name <- IO.eval { scala.io.StdIn.readLine } // simpler with IO.eval
_ <- IO.eval { println(s"Well hello, $name!") }
} yield ()
program.run() // producing the side effects "at the end of the world"
33 / 87
5. Companion object IO with pure and eval
case class IO[A](run: () => A) {
def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() }
def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a)))
def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a)
}
object IO {
def pure[A](value: A): IO[A] = IO { () => value } // eager
def eval[A](thunk: => A): IO[A] = IO { () => thunk } // lazy
}
val program: IO[Unit] = for {
welcome <- IO.pure("Welcome to Scala!")
_ <- IO.eval { print(s"$welcome What's your name? ") }
name <- IO.eval { scala.io.StdIn.readLine } // simpler with IO.eval
_ <- IO.eval { println(s"Well hello, $name!") }
} yield ()
program.run() // producing the side effects "at the end of the world"
IO.pure is eager and accepts a pure value.
IO.eval is lazy and accepts a computation.
map can be written in terms of flatMap and pure.
34 / 87
6. ADT IO
sealed trait IO[+A] {
def run(): A
def flatMap[B](f: A => IO[B]): IO[B] = IO { f(run()).run() }
def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a)))
}
object IO {
// ADT sub types
private case class Pure[A](thunk: () => A) extends IO[A] {
override def run(): A = thunk()
}
private case class Eval[A](thunk: () => A) extends IO[A] {
override def run(): A = thunk()
}
def pure[A](a: A): IO[A] = Pure { () => a }
def now[A](a: A): IO[A] = pure(a)
def eval[A](a: => A): IO[A] = Eval { () => a }
def delay[A](a: => A): IO[A] = eval(a)
def apply[A](a: => A): IO[A] = eval(a)
}
35 / 87
6. ADT IO
sealed trait IO[+A] {
def run(): A
def flatMap[B](f: A => IO[B]): IO[B] = IO { f(run()).run() }
def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a)))
}
object IO {
// ADT sub types
private case class Pure[A](thunk: () => A) extends IO[A] {
override def run(): A = thunk()
}
private case class Eval[A](thunk: () => A) extends IO[A] {
override def run(): A = thunk()
}
def pure[A](a: A): IO[A] = Pure { () => a }
def now[A](a: A): IO[A] = pure(a)
def eval[A](a: => A): IO[A] = Eval { () => a }
def delay[A](a: => A): IO[A] = eval(a)
def apply[A](a: => A): IO[A] = eval(a)
}
IO.apply is an alias for IO.eval. (Simplifies the creation of IO instances.)
The app works as before.
36 / 87
7. ADT sub type FlatMap
sealed trait IO[+A] {
def flatMap[B](f: A => IO[B]): IO[B] = FlatMap(this, f)
def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a)))
}
object IO {
// ADT sub types
private case class FlatMap[A, B](src: IO[A], f: A => IO[B]) extends IO[B] {
override def run(): B = f(src.run()).run()
}
}
37 / 87
7. ADT sub type FlatMap
sealed trait IO[+A] {
def flatMap[B](f: A => IO[B]): IO[B] = FlatMap(this, f)
def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a)))
}
object IO {
// ADT sub types
private case class FlatMap[A, B](src: IO[A], f: A => IO[B]) extends IO[B] {
override def run(): B = f(src.run()).run()
}
}
We gained stack-safety by trampolining. (FlatMap is a heap object.)
The app works as before.
38 / 87
IO Monad - Basic Setup
A Function0 wrapped in a case class
Impl of map and flatMap (and flatten)
companion object with pure and other smart constructors
case class converted to ADT
39 / 87
IO Monad - Basic Setup
A Function0 wrapped in a case class
Impl of map and flatMap (and flatten)
companion object with pure and other smart constructors
case class converted to ADT
What is to come?
Sync and async run methods
More smart constructors
Monad instance for IO
Create IO from Try, Either, Future
MonadError instance for IO (IO.raiseError, IO#handleErrorWith)
Bracket instance for IO (IO#bracket)
Sync instance for IO (IO.delay)
40 / 87
8. Sync run* methods
case class IO[A](run: () => A) {
def runToTry: Try[A] = Try { run() }
def runToEither: Either[Throwable, A] = runToTry.toEither
}
41 / 87
8. Sync run* methods
case class IO[A](run: () => A) {
def runToTry: Try[A] = Try { run() }
def runToEither: Either[Throwable, A] = runToTry.toEither
}
// running the program synchronously ...
val value: Unit = program.run() // ()
val tryy: Try[Unit] = program.runToTry // Success(())
val either: Either[Throwable, Unit] = program.runToEither // Right(())
42 / 87
8. Sync run* methods
case class IO[A](run: () => A) {
def runToTry: Try[A] = Try { run() }
def runToEither: Either[Throwable, A] = runToTry.toEither
}
// running the program synchronously ...
val value: Unit = program.run() // ()
val tryy: Try[Unit] = program.runToTry // Success(())
val either: Either[Throwable, Unit] = program.runToEither // Right(())
run may throw an exception.
runToTry and runToEither avoid that.
43 / 87
9. Other example: Authenticate Maggie
def authenticate(username: String, password: String): IO[Boolean] = ???
val checkMaggie: IO[Boolean] = authenticate("maggie", "maggie-pw")
// running checkMaggie synchronously ...
val value: Boolean = checkMaggie.run()
// true
val tryy: Try[Boolean] = checkMaggie.runToTry
// Success(true)
val either: Either[Throwable, Boolean] = checkMaggie.runToEither
// Right(true)
44 / 87
9. Other example: Authenticate Maggie
def authenticate(username: String, password: String): IO[Boolean] = ???
val checkMaggie: IO[Boolean] = authenticate("maggie", "maggie-pw")
// running checkMaggie synchronously ...
val value: Boolean = checkMaggie.run()
// true
val tryy: Try[Boolean] = checkMaggie.runToTry
// Success(true)
val either: Either[Throwable, Boolean] = checkMaggie.runToEither
// Right(true)
The previous example was interactive ... not well suited for async IO.
45 / 87
10. Async run* methods
case class IO[A](run: () => A) {
def runToFuture(implicit ec: ExecutionContext): Future[A] =
Future { run() }
def runOnComplete(callback: Try[A] => Unit)
(implicit ec: ExecutionContext): Unit =
runToFuture onComplete callback
def runAsync(callback: Either[Throwable, A] => Unit)
(implicit ec: ExecutionContext): Unit =
runOnComplete(tryy => callback(tryy.toEither))
}
46 / 87
10. Async run* methods
case class IO[A](run: () => A) {
def runToFuture(implicit ec: ExecutionContext): Future[A] =
Future { run() }
def runOnComplete(callback: Try[A] => Unit)
(implicit ec: ExecutionContext): Unit =
runToFuture onComplete callback
def runAsync(callback: Either[Throwable, A] => Unit)
(implicit ec: ExecutionContext): Unit =
runOnComplete(tryy => callback(tryy.toEither))
}
All async run* methods take an implicit EC.
Unlike Future the EC is not needed to create an IO, only to run it.
47 / 87
Using the async run* methods
def authenticate(username: String, password: String): IO[Boolean] = ???
val checkMaggie: IO[Boolean] = authenticate("maggie", "maggie-pw")
48 / 87
Using the async run* methods
def authenticate(username: String, password: String): IO[Boolean] = ???
val checkMaggie: IO[Boolean] = authenticate("maggie", "maggie-pw")
// running 'checkMaggie' asynchronously ...
implicit val ec: ExecutionContext = ExecutionContext.global
val future: Future[Boolean] = checkMaggie.runToFuture
future onComplete tryCallback //=> true
checkMaggie runOnComplete tryCallback //=> true
checkMaggie runAsync eitherCallback //=> true
49 / 87
Using the async run* methods
def authenticate(username: String, password: String): IO[Boolean] = ???
val checkMaggie: IO[Boolean] = authenticate("maggie", "maggie-pw")
// running 'checkMaggie' asynchronously ...
implicit val ec: ExecutionContext = ExecutionContext.global
val future: Future[Boolean] = checkMaggie.runToFuture
future onComplete tryCallback //=> true
checkMaggie runOnComplete tryCallback //=> true
checkMaggie runAsync eitherCallback //=> true
def tryCallback[A]: Try[A] => Unit = tryy =>
println(tryy.fold(ex => ex.toString, value => value.toString))
def eitherCallback[A]: Either[Throwable, A] => Unit = either =>
println(either.fold(ex => ex.toString, value => value.toString))
50 / 87
11. Async method foreach
case class IO[A](run: () => A) {
def foreach(f: A => Unit)(implicit ec: ExecutionContext): Unit =
runAsync {
case Left(ex) => ec.reportFailure(ex)
case Right(value) => f(value)
}
}
51 / 87
11. Async method foreach
case class IO[A](run: () => A) {
def foreach(f: A => Unit)(implicit ec: ExecutionContext): Unit =
runAsync {
case Left(ex) => ec.reportFailure(ex)
case Right(value) => f(value)
}
}
authenticate("maggie", "maggie-pw") foreach println //=> true
authenticate("maggieXXX", "maggie-pw") foreach println //=> false
authenticate("maggie", "maggie-pwXXX") foreach println //=> false
52 / 87
11. Async method foreach
case class IO[A](run: () => A) {
def foreach(f: A => Unit)(implicit ec: ExecutionContext): Unit =
runAsync {
case Left(ex) => ec.reportFailure(ex)
case Right(value) => f(value)
}
}
authenticate("maggie", "maggie-pw") foreach println //=> true
authenticate("maggieXXX", "maggie-pw") foreach println //=> false
authenticate("maggie", "maggie-pwXXX") foreach println //=> false
foreach runs asynchronously.
It takes a callback for the successful result value.
foreach swallows exceptions. Prefer runAsync!
53 / 87
12. IO.raiseError
object IO {
// ADT sub types
private case class Error[A](exception: Throwable) extends IO[A] {
override def run(): A = throw exception
}
def raiseError[A](exception: Exception): IO[A] = Error[A](exception)
}
54 / 87
12. IO.raiseError
object IO {
// ADT sub types
private case class Error[A](exception: Throwable) extends IO[A] {
override def run(): A = throw exception
}
def raiseError[A](exception: Exception): IO[A] = Error[A](exception)
}
ADT sub type Error wraps a Throwable.
55 / 87
12. IO.raiseError
object IO {
// ADT sub types
private case class Error[A](exception: Throwable) extends IO[A] {
override def run(): A = throw exception
}
def raiseError[A](exception: Exception): IO[A] = Error[A](exception)
}
ADT sub type Error wraps a Throwable.
val ioError: IO[Int] = IO.raiseError[Int](
new IllegalStateException("illegal state"))
println(ioError.runToEither)
//=> Left(java.lang.IllegalStateException: illegal state)
56 / 87
13. IO#failed (returns a failed projection).
sealed trait IO[+A] {
def failed: IO[Throwable] = Failed(this)
}
object IO {
// ADT sub types
private case class Failed[A](io: IO[A]) extends IO[Throwable] {
override def run(): Throwable = try {
io.run()
throw new NoSuchElementException("failed")
} catch {
case nse: NoSuchElementException if nse.getMessage == "failed" =>
throw nse
case throwable: Throwable =>
throwable
}
}
}
57 / 87
13. IO#failed (returns a failed projection).
sealed trait IO[+A] {
def failed: IO[Throwable] = Failed(this)
}
object IO {
// ADT sub types
private case class Failed[A](io: IO[A]) extends IO[Throwable] {
override def run(): Throwable = try {
io.run()
throw new NoSuchElementException("failed")
} catch {
case nse: NoSuchElementException if nse.getMessage == "failed" =>
throw nse
case throwable: Throwable =>
throwable
}
}
}
ADT sub type Failed wraps the IO to project.
58 / 87
Using IO#failed
val ioError: IO[Int] = IO.raiseError[Int](
new IllegalStateException("illegal state"))
println(ioError.runToEither)
//=> Left(java.lang.IllegalStateException: illegal state)
val failed: IO[Throwable] = ioError.failed
println(failed.runToEither)
//=> Right(java.lang.IllegalStateException: illegal state)
val ioSuccess = IO.pure(5)
println(ioSuccess.runToEither)
//=> Right(5)
println(ioSuccess.failed.runToEither)
//=> Left(java.util.NoSuchElementException: failed)
59 / 87
Using IO#failed
val ioError: IO[Int] = IO.raiseError[Int](
new IllegalStateException("illegal state"))
println(ioError.runToEither)
//=> Left(java.lang.IllegalStateException: illegal state)
val failed: IO[Throwable] = ioError.failed
println(failed.runToEither)
//=> Right(java.lang.IllegalStateException: illegal state)
val ioSuccess = IO.pure(5)
println(ioSuccess.runToEither)
//=> Right(5)
println(ioSuccess.failed.runToEither)
//=> Left(java.util.NoSuchElementException: failed)
The failed projection is an IO holding a value of type Throwable, emitting the
error yielded by the source, in case the source fails, otherwise if the source
succeeds the result will fail with a NoSuchElementException.
60 / 87
14. Other example: pure computations
def sumIO(from: Int, to: Int): IO[Int] =
IO { sumOfRange(from, to) }
def fibonacciIO(num: Int): IO[BigInt] =
IO { fibonacci(num) }
def factorialIO(num: Int): IO[BigInt] =
IO { factorial(num) }
def computeIO(from: Int, to: Int): IO[BigInt] =
for {
x <- sumIO(from, to)
y <- fibonacciIO(x)
z <- factorialIO(y.intValue)
} yield z
val io: IO[BigInt] = computeIO(1, 4)
implicit val ec: ExecutionContext = ExecutionContext.global
io foreach { result => println(s"result = $result") }
//=> 6227020800
61 / 87
15. Monad instance for IO
sealed trait IO[+A] {
def flatMap[B](f: A => IO[B]): IO[B] = FlatMap(this, f)
def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a)))
}
object IO {
def pure[A](a: A): IO[A] = Pure { () => a }
implicit val ioMonad: Monad[IO] = new Monad[IO] {
override def pure[A](value: A): IO[A] = IO.pure(value)
override def flatMap[A, B](fa: IO[A])(f: A => IO[B]): IO[B] = fa flatMap f
}
}
62 / 87
15. Monad instance for IO
sealed trait IO[+A] {
def flatMap[B](f: A => IO[B]): IO[B] = FlatMap(this, f)
def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a)))
}
object IO {
def pure[A](a: A): IO[A] = Pure { () => a }
implicit val ioMonad: Monad[IO] = new Monad[IO] {
override def pure[A](value: A): IO[A] = IO.pure(value)
override def flatMap[A, B](fa: IO[A])(f: A => IO[B]): IO[B] = fa flatMap f
}
}
Monad instance defined in companion object (implicit scope)
63 / 87
Computations that abstract over HKT: F[_]: Monad
import scala.language.higherKinds
import cats.syntax.flatMap._
import cats.syntax.functor._
def sumF[F[_]: Monad](from: Int, to: Int): F[Int] =
Monad[F].pure { sumOfRange(from, to) }
def fibonacciF[F[_]: Monad](num: Int): F[BigInt] =
Monad[F].pure { fibonacci(num) }
def factorialF[F[_]: Monad](num: Int): F[BigInt] =
Monad[F].pure { factorial(num) }
def computeF[F[_]: Monad](from: Int, to: Int): F[BigInt] =
for {
x <- sumF(from, to)
y <- fibonacciF(x)
z <- factorialF(y.intValue)
} yield z
This code can be used with IO or any other Monad.
64 / 87
Reify F[_] : MonadF[_] : Monad with IOIO
import scala.concurrent.ExecutionContext.Implicits.global
val io: IO[BigInt] = computeF[IO](1, 4)
io foreach { result => println(s"result = $result") } //=> 6227020800
65 / 87
Reify F[_] : MonadF[_] : Monad with IOIO
import scala.concurrent.ExecutionContext.Implicits.global
val io: IO[BigInt] = computeF[IO](1, 4)
io foreach { result => println(s"result = $result") } //=> 6227020800
Reify F[_] : MonadF[_] : Monad with cats.Idcats.Id
val result: cats.Id[BigInt] = computeF[cats.Id](1, 4)
println(s"result = $result") //=> 6227020800
66 / 87
Reify F[_] : MonadF[_] : Monad with IOIO
import scala.concurrent.ExecutionContext.Implicits.global
val io: IO[BigInt] = computeF[IO](1, 4)
io foreach { result => println(s"result = $result") } //=> 6227020800
Reify F[_] : MonadF[_] : Monad with cats.Idcats.Id
val result: cats.Id[BigInt] = computeF[cats.Id](1, 4)
println(s"result = $result") //=> 6227020800
Reify F[_] : MonadF[_] : Monad with OptionOption
import cats.instances.option._
val maybeResult: Option[BigInt] = computeF[Option](1, 4)
maybeResult foreach { result => println(s"result = $result") } //=> 6227020800
67 / 87
Reify F[_] : MonadF[_] : Monad with IOIO
import scala.concurrent.ExecutionContext.Implicits.global
val io: IO[BigInt] = computeF[IO](1, 4)
io foreach { result => println(s"result = $result") } //=> 6227020800
Reify F[_] : MonadF[_] : Monad with cats.Idcats.Id
val result: cats.Id[BigInt] = computeF[cats.Id](1, 4)
println(s"result = $result") //=> 6227020800
Reify F[_] : MonadF[_] : Monad with OptionOption
import cats.instances.option._
val maybeResult: Option[BigInt] = computeF[Option](1, 4)
maybeResult foreach { result => println(s"result = $result") } //=> 6227020800
Reify F[_] : MonadF[_] : Monad with FutureFuture
import scala.concurrent.{Future, ExecutionContext}
import ExecutionContext.Implicits.global
import cats.instances.future._
val future: Future[BigInt] = computeF[Future](1, 4)
future foreach { result => println(s"result = $result") } //=> 6227020800
68 / 87
16. IO.defer and IO.suspend
object IO {
// ADT sub types
private case class Suspend[A](thunk: () => IO[A]) extends IO[A] {
override def run(): A = thunk().run()
}
def suspend[A](ioa: => IO[A]): IO[A] = Suspend(() => ioa)
def defer[A](ioa: => IO[A]): IO[A] = suspend(ioa)
}
69 / 87
16. IO.defer and IO.suspend
object IO {
// ADT sub types
private case class Suspend[A](thunk: () => IO[A]) extends IO[A] {
override def run(): A = thunk().run()
}
def suspend[A](ioa: => IO[A]): IO[A] = Suspend(() => ioa)
def defer[A](ioa: => IO[A]): IO[A] = suspend(ioa)
}
These methods defer the (possibly immediate) side effect of the inner IO.
ADT sub type Suspend wraps another IO.
70 / 87
Using IO.defer
IO.pure(...) // without IO.defer
val io1 = IO.pure { println("immediate side effect"); 5 }
//=> immediate side effect
Thread sleep 2000L
io1 foreach println
//=> 5
71 / 87
Using IO.defer
IO.pure(...) // without IO.defer
val io1 = IO.pure { println("immediate side effect"); 5 }
//=> immediate side effect
Thread sleep 2000L
io1 foreach println
//=> 5
IO.defer(IO.pure(...))
val io2 = IO.defer { IO.pure { println("deferred side effect"); 5 } }
Thread sleep 2000L
io2 foreach println
//=> deferred side effect
//=> 5
72 / 87
17. IO.fromTry and IO.fromEither
object IO {
def fromTry[A](tryy: Try[A]): IO[A] =
tryy.fold(IO.raiseError, IO.pure)
def fromEither[A](either: Either[Throwable, A]): IO[A] =
either.fold(IO.raiseError, IO.pure)
}
73 / 87
17. IO.fromTry and IO.fromEither
object IO {
def fromTry[A](tryy: Try[A]): IO[A] =
tryy.fold(IO.raiseError, IO.pure)
def fromEither[A](either: Either[Throwable, A]): IO[A] =
either.fold(IO.raiseError, IO.pure)
}
val tryy: Try[Seq[User]] = Try { User.getUsers }
val io1: IO[Seq[User]] = IO.fromTry(tryy)
val either: Either[Throwable, Seq[User]] = tryy.toEither
val io2: IO[Seq[User]] = IO.fromEither(either)
74 / 87
18. IO.fromFuture
object IO {
// ADT sub types
private case class FromFuture[A](fa: Future[A]) extends IO[A] {
override def run(): A = Await.result(fa, Duration.Inf) // BLOCKING!!!
}
def fromFuture[A](future: Future[A]): IO[A] = FromFuture(future)
}
75 / 87
18. IO.fromFuture
object IO {
// ADT sub types
private case class FromFuture[A](fa: Future[A]) extends IO[A] {
override def run(): A = Await.result(fa, Duration.Inf) // BLOCKING!!!
}
def fromFuture[A](future: Future[A]): IO[A] = FromFuture(future)
}
Attention: The implementation of fromFuture is a bit simplistic.
Waiting for the Future to complete might block a thread!
(A solution of this problem would require a redesign of my simple IO
Monad, which doesn't support non-blocking async computations.)
76 / 87
Using IO.fromFuture
def futureGetUsers(implicit ec: ExecutionContext): Future[Seq[User]] =
Future {
println("side effect")
User.getUsers
}
77 / 87
Using IO.fromFuture
def futureGetUsers(implicit ec: ExecutionContext): Future[Seq[User]] =
Future {
println("side effect")
User.getUsers
}
IO.fromFuture(f) is eager.
implicit val ec: ExecutionContext = ExecutionContext.global
val io = IO.fromFuture { futureGetUsers }
io foreach { users => users foreach println } //=> "side effect"
io foreach { users => users foreach println }
78 / 87
Using IO.fromFuture
def futureGetUsers(implicit ec: ExecutionContext): Future[Seq[User]] =
Future {
println("side effect")
User.getUsers
}
IO.fromFuture(f) is eager.
implicit val ec: ExecutionContext = ExecutionContext.global
val io = IO.fromFuture { futureGetUsers }
io foreach { users => users foreach println } //=> "side effect"
io foreach { users => users foreach println }
IO.defer(IO.fromFuture(f)) is lazy.
implicit val ec: ExecutionContext = ExecutionContext.global
val io = IO.defer { IO.fromFuture { futureGetUsers } }
io foreach { users => users foreach println } //=> "side effect"
io foreach { users => users foreach println } //=> "side effect"
79 / 87
19. IO.deferFuture
object IO {
def deferFuture[A](fa: => Future[A]): IO[A] =
defer(IO.fromFuture(fa))
}
80 / 87
19. IO.deferFuture
object IO {
def deferFuture[A](fa: => Future[A]): IO[A] =
defer(IO.fromFuture(fa))
}
IO.deferFuture(f) is an alias for IO.defer(IO.fromFuture(f)).
An ExecutionContext is still required to create the Future!
81 / 87
Using IO.deferFuture
def futureGetUsers(implicit ec: ExecutionContext): Future[Seq[User]] =
Future {
println("side effect")
User.getUsers
}
82 / 87
Using IO.deferFuture
def futureGetUsers(implicit ec: ExecutionContext): Future[Seq[User]] =
Future {
println("side effect")
User.getUsers
}
IO.defer(IO.fromFuture(f)) is lazy.
implicit val ec: ExecutionContext = ExecutionContext.global
val io = IO.defer { IO.fromFuture { futureGetUsers } }
io foreach { users => users foreach println } //=> "side effect"
io foreach { users => users foreach println } //=> "side effect"
83 / 87
Using IO.deferFuture
def futureGetUsers(implicit ec: ExecutionContext): Future[Seq[User]] =
Future {
println("side effect")
User.getUsers
}
IO.defer(IO.fromFuture(f)) is lazy.
implicit val ec: ExecutionContext = ExecutionContext.global
val io = IO.defer { IO.fromFuture { futureGetUsers } }
io foreach { users => users foreach println } //=> "side effect"
io foreach { users => users foreach println } //=> "side effect"
IO.deferFuture(f) is a shortcut for that.
implicit val ec: ExecutionContext = ExecutionContext.global
val io = IO.deferFuture { futureGetUsers }
io foreach { users => users foreach println } //=> "side effect"
io foreach { users => users foreach println } //=> "side effect"
84 / 87
4. Resources
85 / 87
Resources
Code and Slides of this Talk:
https://github.com/hermannhueck/implementing-io-monad
Code and Slides for my Talk on: Future vs. Monix Task
https://github.com/hermannhueck/future-vs-monix-task
Monix Task 3.x Documentation (for comparison with IO)
https://monix.io/docs/3x/eval/task.html
Monix Task 3.x API Documentation (for comparison with IO)
https://monix.io/api/3.0/monix/eval/Task.html
Best Practice: "Should Not Block Threads"
https://monix.io/docs/3x/best-practices/blocking.html
What Referential Transparency can do for you
Talk by Luka Jacobowitz at ScalaIO 2017
https://www.youtube.com/watch?v=X-cEGEJMx_4
86 / 87
Thanks for Listening
Q & A
https://github.com/hermannhueck/implementing-io-monad
87 / 87
Implementing the IO Monad in Scala

Contenu connexe

Tendances

Ad hoc Polymorphism using Type Classes and Cats
Ad hoc Polymorphism using Type Classes and CatsAd hoc Polymorphism using Type Classes and Cats
Ad hoc Polymorphism using Type Classes and CatsPhilip Schwarz
 
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in ScalaExploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in ScalaJorge Vásquez
 
Refactoring Functional Type Classes
Refactoring Functional Type ClassesRefactoring Functional Type Classes
Refactoring Functional Type ClassesJohn De Goes
 
The Functional Programming Triad of Map, Filter and Fold
The Functional Programming Triad of Map, Filter and FoldThe Functional Programming Triad of Map, Filter and Fold
The Functional Programming Triad of Map, Filter and FoldPhilip Schwarz
 
Sequence and Traverse - Part 2
Sequence and Traverse - Part 2Sequence and Traverse - Part 2
Sequence and Traverse - Part 2Philip Schwarz
 
ZIO: Powerful and Principled Functional Programming in Scala
ZIO: Powerful and Principled Functional Programming in ScalaZIO: Powerful and Principled Functional Programming in Scala
ZIO: Powerful and Principled Functional Programming in ScalaWiem Zine Elabidine
 
Why The Free Monad isn't Free
Why The Free Monad isn't FreeWhy The Free Monad isn't Free
Why The Free Monad isn't FreeKelley Robinson
 
The aggregate function - from sequential and parallel folds to parallel aggre...
The aggregate function - from sequential and parallel folds to parallel aggre...The aggregate function - from sequential and parallel folds to parallel aggre...
The aggregate function - from sequential and parallel folds to parallel aggre...Philip Schwarz
 
Capabilities for Resources and Effects
Capabilities for Resources and EffectsCapabilities for Resources and Effects
Capabilities for Resources and EffectsMartin Odersky
 
Peeking inside the engine of ZIO SQL.pdf
Peeking inside the engine of ZIO SQL.pdfPeeking inside the engine of ZIO SQL.pdf
Peeking inside the engine of ZIO SQL.pdfJaroslavRegec1
 
Blazing Fast, Pure Effects without Monads — LambdaConf 2018
Blazing Fast, Pure Effects without Monads — LambdaConf 2018Blazing Fast, Pure Effects without Monads — LambdaConf 2018
Blazing Fast, Pure Effects without Monads — LambdaConf 2018John De Goes
 
Monad as functor with pair of natural transformations
Monad as functor with pair of natural transformationsMonad as functor with pair of natural transformations
Monad as functor with pair of natural transformationsPhilip Schwarz
 
Boost your productivity with Scala tooling!
Boost your productivity  with Scala tooling!Boost your productivity  with Scala tooling!
Boost your productivity with Scala tooling!MeriamLachkar1
 
Why functional programming and category theory strongly matters
Why functional programming and category theory strongly mattersWhy functional programming and category theory strongly matters
Why functional programming and category theory strongly mattersPiotr Paradziński
 
Quill vs Slick Smackdown
Quill vs Slick SmackdownQuill vs Slick Smackdown
Quill vs Slick SmackdownAlexander Ioffe
 
Embedding Generic Monadic Transformer into Scala. [Tfp2022]
Embedding Generic Monadic Transformer into Scala. [Tfp2022]Embedding Generic Monadic Transformer into Scala. [Tfp2022]
Embedding Generic Monadic Transformer into Scala. [Tfp2022]Ruslan Shevchenko
 
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...Philip Schwarz
 

Tendances (20)

Ad hoc Polymorphism using Type Classes and Cats
Ad hoc Polymorphism using Type Classes and CatsAd hoc Polymorphism using Type Classes and Cats
Ad hoc Polymorphism using Type Classes and Cats
 
ZIO Queue
ZIO QueueZIO Queue
ZIO Queue
 
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in ScalaExploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in Scala
 
Refactoring Functional Type Classes
Refactoring Functional Type ClassesRefactoring Functional Type Classes
Refactoring Functional Type Classes
 
The Functional Programming Triad of Map, Filter and Fold
The Functional Programming Triad of Map, Filter and FoldThe Functional Programming Triad of Map, Filter and Fold
The Functional Programming Triad of Map, Filter and Fold
 
Sequence and Traverse - Part 2
Sequence and Traverse - Part 2Sequence and Traverse - Part 2
Sequence and Traverse - Part 2
 
ZIO: Powerful and Principled Functional Programming in Scala
ZIO: Powerful and Principled Functional Programming in ScalaZIO: Powerful and Principled Functional Programming in Scala
ZIO: Powerful and Principled Functional Programming in Scala
 
Why The Free Monad isn't Free
Why The Free Monad isn't FreeWhy The Free Monad isn't Free
Why The Free Monad isn't Free
 
The aggregate function - from sequential and parallel folds to parallel aggre...
The aggregate function - from sequential and parallel folds to parallel aggre...The aggregate function - from sequential and parallel folds to parallel aggre...
The aggregate function - from sequential and parallel folds to parallel aggre...
 
Capabilities for Resources and Effects
Capabilities for Resources and EffectsCapabilities for Resources and Effects
Capabilities for Resources and Effects
 
Peeking inside the engine of ZIO SQL.pdf
Peeking inside the engine of ZIO SQL.pdfPeeking inside the engine of ZIO SQL.pdf
Peeking inside the engine of ZIO SQL.pdf
 
Blazing Fast, Pure Effects without Monads — LambdaConf 2018
Blazing Fast, Pure Effects without Monads — LambdaConf 2018Blazing Fast, Pure Effects without Monads — LambdaConf 2018
Blazing Fast, Pure Effects without Monads — LambdaConf 2018
 
Monad as functor with pair of natural transformations
Monad as functor with pair of natural transformationsMonad as functor with pair of natural transformations
Monad as functor with pair of natural transformations
 
Boost your productivity with Scala tooling!
Boost your productivity  with Scala tooling!Boost your productivity  with Scala tooling!
Boost your productivity with Scala tooling!
 
Why functional programming and category theory strongly matters
Why functional programming and category theory strongly mattersWhy functional programming and category theory strongly matters
Why functional programming and category theory strongly matters
 
Quill vs Slick Smackdown
Quill vs Slick SmackdownQuill vs Slick Smackdown
Quill vs Slick Smackdown
 
Embedding Generic Monadic Transformer into Scala. [Tfp2022]
Embedding Generic Monadic Transformer into Scala. [Tfp2022]Embedding Generic Monadic Transformer into Scala. [Tfp2022]
Embedding Generic Monadic Transformer into Scala. [Tfp2022]
 
scalar.pdf
scalar.pdfscalar.pdf
scalar.pdf
 
Applicative Functor
Applicative FunctorApplicative Functor
Applicative Functor
 
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
 

Similaire à Implementing the IO Monad in Scala

Future vs. Monix Task
Future vs. Monix TaskFuture vs. Monix Task
Future vs. Monix TaskHermann Hueck
 
cats.effect.IO - Scala Vienna Meetup February 2019
cats.effect.IO - Scala Vienna Meetup February 2019cats.effect.IO - Scala Vienna Meetup February 2019
cats.effect.IO - Scala Vienna Meetup February 2019Daniel Pfeiffer
 
20191116 custom operators in swift
20191116 custom operators in swift20191116 custom operators in swift
20191116 custom operators in swiftChiwon Song
 
Kotlin for android developers whats new
Kotlin for android developers whats newKotlin for android developers whats new
Kotlin for android developers whats newSerghii Chaban
 
Simple IO Monad in 'Functional Programming in Scala'
Simple IO Monad in 'Functional Programming in Scala'Simple IO Monad in 'Functional Programming in Scala'
Simple IO Monad in 'Functional Programming in Scala'Philip Schwarz
 
Akka Futures and Akka Remoting
Akka Futures  and Akka RemotingAkka Futures  and Akka Remoting
Akka Futures and Akka RemotingKnoldus Inc.
 
Talk - Query monad
Talk - Query monad Talk - Query monad
Talk - Query monad Fabernovel
 
EcmaScript 6 - The future is here
EcmaScript 6 - The future is hereEcmaScript 6 - The future is here
EcmaScript 6 - The future is hereSebastiano Armeli
 
Being functional in PHP
Being functional in PHPBeing functional in PHP
Being functional in PHPDavid de Boer
 
Un dsl pour ma base de données
Un dsl pour ma base de donnéesUn dsl pour ma base de données
Un dsl pour ma base de donnéesRomain Lecomte
 
Kotlin For Android - Functions (part 3 of 7)
Kotlin For Android - Functions (part 3 of 7)Kotlin For Android - Functions (part 3 of 7)
Kotlin For Android - Functions (part 3 of 7)Gesh Markov
 
Origins of Elixir programming language
Origins of Elixir programming languageOrigins of Elixir programming language
Origins of Elixir programming languagePivorak MeetUp
 
EcmaScript unchained
EcmaScript unchainedEcmaScript unchained
EcmaScript unchainedEduard Tomàs
 
Functions, Types, Programs and Effects
Functions, Types, Programs and EffectsFunctions, Types, Programs and Effects
Functions, Types, Programs and EffectsRaymond Roestenburg
 
Introduction to Functional Programming with Scala
Introduction to Functional Programming with ScalaIntroduction to Functional Programming with Scala
Introduction to Functional Programming with Scalapramode_ce
 
Things about Functional JavaScript
Things about Functional JavaScriptThings about Functional JavaScript
Things about Functional JavaScriptChengHui Weng
 

Similaire à Implementing the IO Monad in Scala (20)

Future vs. Monix Task
Future vs. Monix TaskFuture vs. Monix Task
Future vs. Monix Task
 
cats.effect.IO - Scala Vienna Meetup February 2019
cats.effect.IO - Scala Vienna Meetup February 2019cats.effect.IO - Scala Vienna Meetup February 2019
cats.effect.IO - Scala Vienna Meetup February 2019
 
20191116 custom operators in swift
20191116 custom operators in swift20191116 custom operators in swift
20191116 custom operators in swift
 
ZIO Queue
ZIO QueueZIO Queue
ZIO Queue
 
Kotlin for android developers whats new
Kotlin for android developers whats newKotlin for android developers whats new
Kotlin for android developers whats new
 
Simple IO Monad in 'Functional Programming in Scala'
Simple IO Monad in 'Functional Programming in Scala'Simple IO Monad in 'Functional Programming in Scala'
Simple IO Monad in 'Functional Programming in Scala'
 
Akka Futures and Akka Remoting
Akka Futures  and Akka RemotingAkka Futures  and Akka Remoting
Akka Futures and Akka Remoting
 
Talk - Query monad
Talk - Query monad Talk - Query monad
Talk - Query monad
 
Zio from Home
Zio from Home Zio from Home
Zio from Home
 
EcmaScript 6 - The future is here
EcmaScript 6 - The future is hereEcmaScript 6 - The future is here
EcmaScript 6 - The future is here
 
Being functional in PHP
Being functional in PHPBeing functional in PHP
Being functional in PHP
 
Un dsl pour ma base de données
Un dsl pour ma base de donnéesUn dsl pour ma base de données
Un dsl pour ma base de données
 
Kotlin For Android - Functions (part 3 of 7)
Kotlin For Android - Functions (part 3 of 7)Kotlin For Android - Functions (part 3 of 7)
Kotlin For Android - Functions (part 3 of 7)
 
Dallas Scala Meetup
Dallas Scala MeetupDallas Scala Meetup
Dallas Scala Meetup
 
Origins of Elixir programming language
Origins of Elixir programming languageOrigins of Elixir programming language
Origins of Elixir programming language
 
EcmaScript unchained
EcmaScript unchainedEcmaScript unchained
EcmaScript unchained
 
Functions, Types, Programs and Effects
Functions, Types, Programs and EffectsFunctions, Types, Programs and Effects
Functions, Types, Programs and Effects
 
Introduction to Functional Programming with Scala
Introduction to Functional Programming with ScalaIntroduction to Functional Programming with Scala
Introduction to Functional Programming with Scala
 
Advanced JavaScript
Advanced JavaScript Advanced JavaScript
Advanced JavaScript
 
Things about Functional JavaScript
Things about Functional JavaScriptThings about Functional JavaScript
Things about Functional JavaScript
 

Plus de Hermann Hueck

What's new in Scala 2.13?
What's new in Scala 2.13?What's new in Scala 2.13?
What's new in Scala 2.13?Hermann Hueck
 
Use Applicative where applicable!
Use Applicative where applicable!Use Applicative where applicable!
Use Applicative where applicable!Hermann Hueck
 
From Function1#apply to Kleisli
From Function1#apply to KleisliFrom Function1#apply to Kleisli
From Function1#apply to KleisliHermann Hueck
 
From Functor Composition to Monad Transformers
From Functor Composition to Monad TransformersFrom Functor Composition to Monad Transformers
From Functor Composition to Monad TransformersHermann Hueck
 
Type Classes in Scala and Haskell
Type Classes in Scala and HaskellType Classes in Scala and Haskell
Type Classes in Scala and HaskellHermann Hueck
 
Reactive Access to MongoDB from Scala
Reactive Access to MongoDB from ScalaReactive Access to MongoDB from Scala
Reactive Access to MongoDB from ScalaHermann Hueck
 
Reactive Access to MongoDB from Java 8
Reactive Access to MongoDB from Java 8Reactive Access to MongoDB from Java 8
Reactive Access to MongoDB from Java 8Hermann Hueck
 
Implementing a many-to-many Relationship with Slick
Implementing a many-to-many Relationship with SlickImplementing a many-to-many Relationship with Slick
Implementing a many-to-many Relationship with SlickHermann Hueck
 

Plus de Hermann Hueck (10)

A Taste of Dotty
A Taste of DottyA Taste of Dotty
A Taste of Dotty
 
What's new in Scala 2.13?
What's new in Scala 2.13?What's new in Scala 2.13?
What's new in Scala 2.13?
 
Pragmatic sbt
Pragmatic sbtPragmatic sbt
Pragmatic sbt
 
Use Applicative where applicable!
Use Applicative where applicable!Use Applicative where applicable!
Use Applicative where applicable!
 
From Function1#apply to Kleisli
From Function1#apply to KleisliFrom Function1#apply to Kleisli
From Function1#apply to Kleisli
 
From Functor Composition to Monad Transformers
From Functor Composition to Monad TransformersFrom Functor Composition to Monad Transformers
From Functor Composition to Monad Transformers
 
Type Classes in Scala and Haskell
Type Classes in Scala and HaskellType Classes in Scala and Haskell
Type Classes in Scala and Haskell
 
Reactive Access to MongoDB from Scala
Reactive Access to MongoDB from ScalaReactive Access to MongoDB from Scala
Reactive Access to MongoDB from Scala
 
Reactive Access to MongoDB from Java 8
Reactive Access to MongoDB from Java 8Reactive Access to MongoDB from Java 8
Reactive Access to MongoDB from Java 8
 
Implementing a many-to-many Relationship with Slick
Implementing a many-to-many Relationship with SlickImplementing a many-to-many Relationship with Slick
Implementing a many-to-many Relationship with Slick
 

Dernier

SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AIABDERRAOUF MEHENNI
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...OnePlan Solutions
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...Health
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Modelsaagamshah0812
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Steffen Staab
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsAndolasoft Inc
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
 

Dernier (20)

SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
 
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.js
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 

Implementing the IO Monad in Scala

  • 1. Implementing the IO Monad © 2019 Hermann Hueck https://github.com/hermannhueck/implementing-io-monad 1 / 87
  • 2. Abstract With my simple implementation I wanted to demonstrate the basic ideas of th IO Monad. My impl of the IO Monad is just a feasibility study, not production code! When coding my impl of IO I was very much inspired by cats.effect.IO and monix.eval.Task which I studied at that time. Both are implementions of the IO Monad. The API of my IO is very similar to the basics of Monix Task. This IO implementation also helped me to understand the IO Monad (of cats-effect) and Monix Task. Interop with Future is also supported. You can convert IO to a Future. Vice versa you can convert a Future to an IO. The development of my impl can be followed step by step in the code files in package iomonad. 2 / 87
  • 3. Agenda 1. Referential Transparency 2. Is Future referentially transparent? 3. The IO Monad 4. Resources 3 / 87
  • 5. Referential Transparency An expression is called referentially transparent if it can be replaced with its corresponding value without changing the program's behavior. This requires that the expression is purepure, that is to say the expression value must be the same for the same inputs and its evaluation must have no side effectsno side effects. https://en.wikipedia.org/wiki/Referential_transparency 5 / 87
  • 6. Referential Transparency Benefits (Equational) Reasoning about code Refactoring is easier Testing is easier Separate pure code from impure code Potential compiler optimizations (more in Haskell than in Scala) (e.g. memoization, parallelisation, compute expressions at compile time) "What Referential Transparency can do for you" Talk by Luka Jacobowitz at ScalaIO 2017 https://www.youtube.com/watch?v=X-cEGEJMx_4 6 / 87
  • 7. func is not referentially transparent! def func(ioa1: Unit, ioa2: Unit): Unit = { ioa1 ioa2 } func(println("hi"), println("hi")) // prints "hi" twice //=> hi //=> hi println("-----") val x: Unit = println("hi") func(x, x) // prints "hi" once //=> hi 7 / 87
  • 8. func is referentially transparent! def putStrLn(line: String): IO[Unit] = IO.eval { println(line) } def func(ioa1: IO[Unit], ioa2: IO[Unit]): IO[Unit] = for { _ <- ioa1 _ <- ioa2 } yield () func(putStrLn("hi"), putStrLn("hi")).run() // prints "hi" twice //=> hi //=> hi println("-----") val x: IO[Unit] = putStrLn("hi") func(x, x).run() // prints "hi" twice //=> hi //=> hi 8 / 87
  • 9. 2. Is Future referentially transparent? 9 / 87
  • 10. Is Future referentially transparent? val future1: Future[(Int, Int)] = { val atomicInt = new AtomicInteger(0) val future: Future[Int] = Future { atomicInt.incrementAndGet } for { x <- future y <- future } yield (x, y) } future1 onComplete println // Success((1,1)) // same as future1, but inlined val future2: Future[(Int, Int)] = { val atomicInt = new AtomicInteger(0) for { x <- Future { atomicInt.incrementAndGet } y <- Future { atomicInt.incrementAndGet } } yield (x, y) } future2 onComplete println // Success((1,2)) <-- not the same result 10 / 87
  • 11. Is Future referentially transparent? val future1: Future[(Int, Int)] = { val atomicInt = new AtomicInteger(0) val future: Future[Int] = Future { atomicInt.incrementAndGet } for { x <- future y <- future } yield (x, y) } future1 onComplete println // Success((1,1)) // same as future1, but inlined val future2: Future[(Int, Int)] = { val atomicInt = new AtomicInteger(0) for { x <- Future { atomicInt.incrementAndGet } y <- Future { atomicInt.incrementAndGet } } yield (x, y) } future2 onComplete println // Success((1,2)) <-- not the same result No! 11 / 87
  • 12. Is Monix Task referentially transparent? val task1: Task[(Int, Int)] = { val atomicInt = new AtomicInteger(0) val task: Task[Int] = Task { atomicInt.incrementAndGet } for { x <- task y <- task } yield (x, y) } task1 runAsync println // Success((1,2)) // same as task1, but inlined val task2: Task[(Int, Int)] = { val atomicInt = new AtomicInteger(0) for { x <- Task { atomicInt.incrementAndGet } y <- Task { atomicInt.incrementAndGet } } yield (x, y) } task2 runAsync println // Success((1,2)) <-- same result 12 / 87
  • 13. Is Monix Task referentially transparent? val task1: Task[(Int, Int)] = { val atomicInt = new AtomicInteger(0) val task: Task[Int] = Task { atomicInt.incrementAndGet } for { x <- task y <- task } yield (x, y) } task1 runAsync println // Success((1,2)) // same as task1, but inlined val task2: Task[(Int, Int)] = { val atomicInt = new AtomicInteger(0) for { x <- Task { atomicInt.incrementAndGet } y <- Task { atomicInt.incrementAndGet } } yield (x, y) } task2 runAsync println // Success((1,2)) <-- same result Yes! 13 / 87
  • 14. Is my IO Monad referentially transparent? val io1: IO[(Int, Int)] = { val atomicInt = new AtomicInteger(0) val io: IO[Int] = IO { atomicInt.incrementAndGet } for { x <- io y <- io } yield (x, y) } io1.runToFuture onComplete println // Success((1,2)) // same as io1, but inlined val io2: IO[(Int, Int)] = { val atomicInt = new AtomicInteger(0) for { x <- IO { atomicInt.incrementAndGet } y <- IO { atomicInt.incrementAndGet } } yield (x, y) } io2.runToFuture onComplete println // Success((1,2)) <-- same result 14 / 87
  • 15. Is my IO Monad referentially transparent? val io1: IO[(Int, Int)] = { val atomicInt = new AtomicInteger(0) val io: IO[Int] = IO { atomicInt.incrementAndGet } for { x <- io y <- io } yield (x, y) } io1.runToFuture onComplete println // Success((1,2)) // same as io1, but inlined val io2: IO[(Int, Int)] = { val atomicInt = new AtomicInteger(0) for { x <- IO { atomicInt.incrementAndGet } y <- IO { atomicInt.incrementAndGet } } yield (x, y) } io2.runToFuture onComplete println // Success((1,2)) <-- same result Yes! 15 / 87
  • 16. 3. The IO Monad 16 / 87
  • 17. 1. Impure IO Program with side e!ects // impure program def program(): Unit = { print("Welcome to Scala! What's your name? ") val name = scala.io.StdIn.readLine println(s"Well hello, $name!") } 17 / 87
  • 18. 1. Impure IO Program with side e!ects // impure program def program(): Unit = { print("Welcome to Scala! What's your name? ") val name = scala.io.StdIn.readLine println(s"Well hello, $name!") } program() 18 / 87
  • 19. 1. Impure IO Program with side e!ects // impure program def program(): Unit = { print("Welcome to Scala! What's your name? ") val name = scala.io.StdIn.readLine println(s"Well hello, $name!") } program() Whenever a method or a function returns Unit it is impureimpure (or it is a noop). It's intension is to produce a side effect. A purepure function always returns a value of some type (and doesn't produce a side effect inside). 19 / 87
  • 20. 2. Pure IO Program without side e!ects // pure program val program: () => Unit = // () => Unit is syntactic sugar for: Function0[Unit] () => { print("Welcome to Scala! What's your name? ") val name = scala.io.StdIn.readLine println(s"Well hello, $name!") } 20 / 87
  • 21. 2. Pure IO Program without side e!ects // pure program val program: () => Unit = // () => Unit is syntactic sugar for: Function0[Unit] () => { print("Welcome to Scala! What's your name? ") val name = scala.io.StdIn.readLine println(s"Well hello, $name!") } program() // producing the side effects "at the end of the world" 21 / 87
  • 22. 2. Pure IO Program without side e!ects // pure program val program: () => Unit = // () => Unit is syntactic sugar for: Function0[Unit] () => { print("Welcome to Scala! What's your name? ") val name = scala.io.StdIn.readLine println(s"Well hello, $name!") } program() // producing the side effects "at the end of the world" Make the program a function returning Unit: Function0[Unit] Free of side effects in it's definition Produces side effects only when run (at the end of the world) program can be a val (if no parameters are passed to it) 22 / 87
  • 23. 3. Wrap Function0[A] in a case class case class IO[A](run: () => A) 23 / 87
  • 24. 3. Wrap Function0[A] in a case class case class IO[A](run: () => A) // pure program val program: IO[Unit] = IO { () => { print("Welcome to Scala! What's your name? ") val name = scala.io.StdIn.readLine println(s"Well hello, $name!") } } 24 / 87
  • 25. 3. Wrap Function0[A] in a case class case class IO[A](run: () => A) // pure program val program: IO[Unit] = IO { () => { print("Welcome to Scala! What's your name? ") val name = scala.io.StdIn.readLine println(s"Well hello, $name!") } } program.run() // producing the side effects "at the end of the world" 25 / 87
  • 26. 3. Wrap Function0[A] in a case class case class IO[A](run: () => A) // pure program val program: IO[Unit] = IO { () => { print("Welcome to Scala! What's your name? ") val name = scala.io.StdIn.readLine println(s"Well hello, $name!") } } program.run() // producing the side effects "at the end of the world" IO[A] wraps a Function0[A] in a case class. This is useful to implement further extensions on that case class. 26 / 87
  • 27. 4. IO#map and IO#flatMap case class IO[A](run: () => A) { def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() } def map[B](f: A => B): IO[B] = IO { () => f(run()) } def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a) } 27 / 87
  • 28. 4. IO#map and IO#flatMap case class IO[A](run: () => A) { def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() } def map[B](f: A => B): IO[B] = IO { () => f(run()) } def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a) } val program: IO[Unit] = for { _ <- IO { () => print(s"Welcome to Scala! What's your name? ") } name <- IO { () => scala.io.StdIn.readLine } _ <- IO { () => println(s"Well hello, $name!") } } yield () 28 / 87
  • 29. 4. IO#map and IO#flatMap case class IO[A](run: () => A) { def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() } def map[B](f: A => B): IO[B] = IO { () => f(run()) } def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a) } val program: IO[Unit] = for { _ <- IO { () => print(s"Welcome to Scala! What's your name? ") } name <- IO { () => scala.io.StdIn.readLine } _ <- IO { () => println(s"Well hello, $name!") } } yield () program.run() // producing the side effects "at the end of the world" 29 / 87
  • 30. 4. IO#map and IO#flatMap case class IO[A](run: () => A) { def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() } def map[B](f: A => B): IO[B] = IO { () => f(run()) } def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a) } val program: IO[Unit] = for { _ <- IO { () => print(s"Welcome to Scala! What's your name? ") } name <- IO { () => scala.io.StdIn.readLine } _ <- IO { () => println(s"Well hello, $name!") } } yield () program.run() // producing the side effects "at the end of the world" With map and flatMap IO[A] is monadic (but it is not yet a Monad). IO is ready for for-comprehensions. This allows the composition of programs from smaller components. 30 / 87
  • 31. 5. Companion object IO with pure and eval case class IO[A](run: () => A) { def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() } def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a))) def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a) } object IO { def pure[A](value: A): IO[A] = IO { () => value } // eager def eval[A](thunk: => A): IO[A] = IO { () => thunk } // lazy } 31 / 87
  • 32. 5. Companion object IO with pure and eval case class IO[A](run: () => A) { def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() } def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a))) def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a) } object IO { def pure[A](value: A): IO[A] = IO { () => value } // eager def eval[A](thunk: => A): IO[A] = IO { () => thunk } // lazy } val program: IO[Unit] = for { welcome <- IO.pure("Welcome to Scala!") _ <- IO.eval { print(s"$welcome What's your name? ") } name <- IO.eval { scala.io.StdIn.readLine } // simpler with IO.eval _ <- IO.eval { println(s"Well hello, $name!") } } yield () 32 / 87
  • 33. 5. Companion object IO with pure and eval case class IO[A](run: () => A) { def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() } def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a))) def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a) } object IO { def pure[A](value: A): IO[A] = IO { () => value } // eager def eval[A](thunk: => A): IO[A] = IO { () => thunk } // lazy } val program: IO[Unit] = for { welcome <- IO.pure("Welcome to Scala!") _ <- IO.eval { print(s"$welcome What's your name? ") } name <- IO.eval { scala.io.StdIn.readLine } // simpler with IO.eval _ <- IO.eval { println(s"Well hello, $name!") } } yield () program.run() // producing the side effects "at the end of the world" 33 / 87
  • 34. 5. Companion object IO with pure and eval case class IO[A](run: () => A) { def flatMap[B](f: A => IO[B]): IO[B] = IO { () => f(run()).run() } def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a))) def flatten[B](implicit ev: A <:< IO[B]): IO[B] = flatMap(a => a) } object IO { def pure[A](value: A): IO[A] = IO { () => value } // eager def eval[A](thunk: => A): IO[A] = IO { () => thunk } // lazy } val program: IO[Unit] = for { welcome <- IO.pure("Welcome to Scala!") _ <- IO.eval { print(s"$welcome What's your name? ") } name <- IO.eval { scala.io.StdIn.readLine } // simpler with IO.eval _ <- IO.eval { println(s"Well hello, $name!") } } yield () program.run() // producing the side effects "at the end of the world" IO.pure is eager and accepts a pure value. IO.eval is lazy and accepts a computation. map can be written in terms of flatMap and pure. 34 / 87
  • 35. 6. ADT IO sealed trait IO[+A] { def run(): A def flatMap[B](f: A => IO[B]): IO[B] = IO { f(run()).run() } def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a))) } object IO { // ADT sub types private case class Pure[A](thunk: () => A) extends IO[A] { override def run(): A = thunk() } private case class Eval[A](thunk: () => A) extends IO[A] { override def run(): A = thunk() } def pure[A](a: A): IO[A] = Pure { () => a } def now[A](a: A): IO[A] = pure(a) def eval[A](a: => A): IO[A] = Eval { () => a } def delay[A](a: => A): IO[A] = eval(a) def apply[A](a: => A): IO[A] = eval(a) } 35 / 87
  • 36. 6. ADT IO sealed trait IO[+A] { def run(): A def flatMap[B](f: A => IO[B]): IO[B] = IO { f(run()).run() } def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a))) } object IO { // ADT sub types private case class Pure[A](thunk: () => A) extends IO[A] { override def run(): A = thunk() } private case class Eval[A](thunk: () => A) extends IO[A] { override def run(): A = thunk() } def pure[A](a: A): IO[A] = Pure { () => a } def now[A](a: A): IO[A] = pure(a) def eval[A](a: => A): IO[A] = Eval { () => a } def delay[A](a: => A): IO[A] = eval(a) def apply[A](a: => A): IO[A] = eval(a) } IO.apply is an alias for IO.eval. (Simplifies the creation of IO instances.) The app works as before. 36 / 87
  • 37. 7. ADT sub type FlatMap sealed trait IO[+A] { def flatMap[B](f: A => IO[B]): IO[B] = FlatMap(this, f) def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a))) } object IO { // ADT sub types private case class FlatMap[A, B](src: IO[A], f: A => IO[B]) extends IO[B] { override def run(): B = f(src.run()).run() } } 37 / 87
  • 38. 7. ADT sub type FlatMap sealed trait IO[+A] { def flatMap[B](f: A => IO[B]): IO[B] = FlatMap(this, f) def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a))) } object IO { // ADT sub types private case class FlatMap[A, B](src: IO[A], f: A => IO[B]) extends IO[B] { override def run(): B = f(src.run()).run() } } We gained stack-safety by trampolining. (FlatMap is a heap object.) The app works as before. 38 / 87
  • 39. IO Monad - Basic Setup A Function0 wrapped in a case class Impl of map and flatMap (and flatten) companion object with pure and other smart constructors case class converted to ADT 39 / 87
  • 40. IO Monad - Basic Setup A Function0 wrapped in a case class Impl of map and flatMap (and flatten) companion object with pure and other smart constructors case class converted to ADT What is to come? Sync and async run methods More smart constructors Monad instance for IO Create IO from Try, Either, Future MonadError instance for IO (IO.raiseError, IO#handleErrorWith) Bracket instance for IO (IO#bracket) Sync instance for IO (IO.delay) 40 / 87
  • 41. 8. Sync run* methods case class IO[A](run: () => A) { def runToTry: Try[A] = Try { run() } def runToEither: Either[Throwable, A] = runToTry.toEither } 41 / 87
  • 42. 8. Sync run* methods case class IO[A](run: () => A) { def runToTry: Try[A] = Try { run() } def runToEither: Either[Throwable, A] = runToTry.toEither } // running the program synchronously ... val value: Unit = program.run() // () val tryy: Try[Unit] = program.runToTry // Success(()) val either: Either[Throwable, Unit] = program.runToEither // Right(()) 42 / 87
  • 43. 8. Sync run* methods case class IO[A](run: () => A) { def runToTry: Try[A] = Try { run() } def runToEither: Either[Throwable, A] = runToTry.toEither } // running the program synchronously ... val value: Unit = program.run() // () val tryy: Try[Unit] = program.runToTry // Success(()) val either: Either[Throwable, Unit] = program.runToEither // Right(()) run may throw an exception. runToTry and runToEither avoid that. 43 / 87
  • 44. 9. Other example: Authenticate Maggie def authenticate(username: String, password: String): IO[Boolean] = ??? val checkMaggie: IO[Boolean] = authenticate("maggie", "maggie-pw") // running checkMaggie synchronously ... val value: Boolean = checkMaggie.run() // true val tryy: Try[Boolean] = checkMaggie.runToTry // Success(true) val either: Either[Throwable, Boolean] = checkMaggie.runToEither // Right(true) 44 / 87
  • 45. 9. Other example: Authenticate Maggie def authenticate(username: String, password: String): IO[Boolean] = ??? val checkMaggie: IO[Boolean] = authenticate("maggie", "maggie-pw") // running checkMaggie synchronously ... val value: Boolean = checkMaggie.run() // true val tryy: Try[Boolean] = checkMaggie.runToTry // Success(true) val either: Either[Throwable, Boolean] = checkMaggie.runToEither // Right(true) The previous example was interactive ... not well suited for async IO. 45 / 87
  • 46. 10. Async run* methods case class IO[A](run: () => A) { def runToFuture(implicit ec: ExecutionContext): Future[A] = Future { run() } def runOnComplete(callback: Try[A] => Unit) (implicit ec: ExecutionContext): Unit = runToFuture onComplete callback def runAsync(callback: Either[Throwable, A] => Unit) (implicit ec: ExecutionContext): Unit = runOnComplete(tryy => callback(tryy.toEither)) } 46 / 87
  • 47. 10. Async run* methods case class IO[A](run: () => A) { def runToFuture(implicit ec: ExecutionContext): Future[A] = Future { run() } def runOnComplete(callback: Try[A] => Unit) (implicit ec: ExecutionContext): Unit = runToFuture onComplete callback def runAsync(callback: Either[Throwable, A] => Unit) (implicit ec: ExecutionContext): Unit = runOnComplete(tryy => callback(tryy.toEither)) } All async run* methods take an implicit EC. Unlike Future the EC is not needed to create an IO, only to run it. 47 / 87
  • 48. Using the async run* methods def authenticate(username: String, password: String): IO[Boolean] = ??? val checkMaggie: IO[Boolean] = authenticate("maggie", "maggie-pw") 48 / 87
  • 49. Using the async run* methods def authenticate(username: String, password: String): IO[Boolean] = ??? val checkMaggie: IO[Boolean] = authenticate("maggie", "maggie-pw") // running 'checkMaggie' asynchronously ... implicit val ec: ExecutionContext = ExecutionContext.global val future: Future[Boolean] = checkMaggie.runToFuture future onComplete tryCallback //=> true checkMaggie runOnComplete tryCallback //=> true checkMaggie runAsync eitherCallback //=> true 49 / 87
  • 50. Using the async run* methods def authenticate(username: String, password: String): IO[Boolean] = ??? val checkMaggie: IO[Boolean] = authenticate("maggie", "maggie-pw") // running 'checkMaggie' asynchronously ... implicit val ec: ExecutionContext = ExecutionContext.global val future: Future[Boolean] = checkMaggie.runToFuture future onComplete tryCallback //=> true checkMaggie runOnComplete tryCallback //=> true checkMaggie runAsync eitherCallback //=> true def tryCallback[A]: Try[A] => Unit = tryy => println(tryy.fold(ex => ex.toString, value => value.toString)) def eitherCallback[A]: Either[Throwable, A] => Unit = either => println(either.fold(ex => ex.toString, value => value.toString)) 50 / 87
  • 51. 11. Async method foreach case class IO[A](run: () => A) { def foreach(f: A => Unit)(implicit ec: ExecutionContext): Unit = runAsync { case Left(ex) => ec.reportFailure(ex) case Right(value) => f(value) } } 51 / 87
  • 52. 11. Async method foreach case class IO[A](run: () => A) { def foreach(f: A => Unit)(implicit ec: ExecutionContext): Unit = runAsync { case Left(ex) => ec.reportFailure(ex) case Right(value) => f(value) } } authenticate("maggie", "maggie-pw") foreach println //=> true authenticate("maggieXXX", "maggie-pw") foreach println //=> false authenticate("maggie", "maggie-pwXXX") foreach println //=> false 52 / 87
  • 53. 11. Async method foreach case class IO[A](run: () => A) { def foreach(f: A => Unit)(implicit ec: ExecutionContext): Unit = runAsync { case Left(ex) => ec.reportFailure(ex) case Right(value) => f(value) } } authenticate("maggie", "maggie-pw") foreach println //=> true authenticate("maggieXXX", "maggie-pw") foreach println //=> false authenticate("maggie", "maggie-pwXXX") foreach println //=> false foreach runs asynchronously. It takes a callback for the successful result value. foreach swallows exceptions. Prefer runAsync! 53 / 87
  • 54. 12. IO.raiseError object IO { // ADT sub types private case class Error[A](exception: Throwable) extends IO[A] { override def run(): A = throw exception } def raiseError[A](exception: Exception): IO[A] = Error[A](exception) } 54 / 87
  • 55. 12. IO.raiseError object IO { // ADT sub types private case class Error[A](exception: Throwable) extends IO[A] { override def run(): A = throw exception } def raiseError[A](exception: Exception): IO[A] = Error[A](exception) } ADT sub type Error wraps a Throwable. 55 / 87
  • 56. 12. IO.raiseError object IO { // ADT sub types private case class Error[A](exception: Throwable) extends IO[A] { override def run(): A = throw exception } def raiseError[A](exception: Exception): IO[A] = Error[A](exception) } ADT sub type Error wraps a Throwable. val ioError: IO[Int] = IO.raiseError[Int]( new IllegalStateException("illegal state")) println(ioError.runToEither) //=> Left(java.lang.IllegalStateException: illegal state) 56 / 87
  • 57. 13. IO#failed (returns a failed projection). sealed trait IO[+A] { def failed: IO[Throwable] = Failed(this) } object IO { // ADT sub types private case class Failed[A](io: IO[A]) extends IO[Throwable] { override def run(): Throwable = try { io.run() throw new NoSuchElementException("failed") } catch { case nse: NoSuchElementException if nse.getMessage == "failed" => throw nse case throwable: Throwable => throwable } } } 57 / 87
  • 58. 13. IO#failed (returns a failed projection). sealed trait IO[+A] { def failed: IO[Throwable] = Failed(this) } object IO { // ADT sub types private case class Failed[A](io: IO[A]) extends IO[Throwable] { override def run(): Throwable = try { io.run() throw new NoSuchElementException("failed") } catch { case nse: NoSuchElementException if nse.getMessage == "failed" => throw nse case throwable: Throwable => throwable } } } ADT sub type Failed wraps the IO to project. 58 / 87
  • 59. Using IO#failed val ioError: IO[Int] = IO.raiseError[Int]( new IllegalStateException("illegal state")) println(ioError.runToEither) //=> Left(java.lang.IllegalStateException: illegal state) val failed: IO[Throwable] = ioError.failed println(failed.runToEither) //=> Right(java.lang.IllegalStateException: illegal state) val ioSuccess = IO.pure(5) println(ioSuccess.runToEither) //=> Right(5) println(ioSuccess.failed.runToEither) //=> Left(java.util.NoSuchElementException: failed) 59 / 87
  • 60. Using IO#failed val ioError: IO[Int] = IO.raiseError[Int]( new IllegalStateException("illegal state")) println(ioError.runToEither) //=> Left(java.lang.IllegalStateException: illegal state) val failed: IO[Throwable] = ioError.failed println(failed.runToEither) //=> Right(java.lang.IllegalStateException: illegal state) val ioSuccess = IO.pure(5) println(ioSuccess.runToEither) //=> Right(5) println(ioSuccess.failed.runToEither) //=> Left(java.util.NoSuchElementException: failed) The failed projection is an IO holding a value of type Throwable, emitting the error yielded by the source, in case the source fails, otherwise if the source succeeds the result will fail with a NoSuchElementException. 60 / 87
  • 61. 14. Other example: pure computations def sumIO(from: Int, to: Int): IO[Int] = IO { sumOfRange(from, to) } def fibonacciIO(num: Int): IO[BigInt] = IO { fibonacci(num) } def factorialIO(num: Int): IO[BigInt] = IO { factorial(num) } def computeIO(from: Int, to: Int): IO[BigInt] = for { x <- sumIO(from, to) y <- fibonacciIO(x) z <- factorialIO(y.intValue) } yield z val io: IO[BigInt] = computeIO(1, 4) implicit val ec: ExecutionContext = ExecutionContext.global io foreach { result => println(s"result = $result") } //=> 6227020800 61 / 87
  • 62. 15. Monad instance for IO sealed trait IO[+A] { def flatMap[B](f: A => IO[B]): IO[B] = FlatMap(this, f) def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a))) } object IO { def pure[A](a: A): IO[A] = Pure { () => a } implicit val ioMonad: Monad[IO] = new Monad[IO] { override def pure[A](value: A): IO[A] = IO.pure(value) override def flatMap[A, B](fa: IO[A])(f: A => IO[B]): IO[B] = fa flatMap f } } 62 / 87
  • 63. 15. Monad instance for IO sealed trait IO[+A] { def flatMap[B](f: A => IO[B]): IO[B] = FlatMap(this, f) def map[B](f: A => B): IO[B] = flatMap(a => pure(f(a))) } object IO { def pure[A](a: A): IO[A] = Pure { () => a } implicit val ioMonad: Monad[IO] = new Monad[IO] { override def pure[A](value: A): IO[A] = IO.pure(value) override def flatMap[A, B](fa: IO[A])(f: A => IO[B]): IO[B] = fa flatMap f } } Monad instance defined in companion object (implicit scope) 63 / 87
  • 64. Computations that abstract over HKT: F[_]: Monad import scala.language.higherKinds import cats.syntax.flatMap._ import cats.syntax.functor._ def sumF[F[_]: Monad](from: Int, to: Int): F[Int] = Monad[F].pure { sumOfRange(from, to) } def fibonacciF[F[_]: Monad](num: Int): F[BigInt] = Monad[F].pure { fibonacci(num) } def factorialF[F[_]: Monad](num: Int): F[BigInt] = Monad[F].pure { factorial(num) } def computeF[F[_]: Monad](from: Int, to: Int): F[BigInt] = for { x <- sumF(from, to) y <- fibonacciF(x) z <- factorialF(y.intValue) } yield z This code can be used with IO or any other Monad. 64 / 87
  • 65. Reify F[_] : MonadF[_] : Monad with IOIO import scala.concurrent.ExecutionContext.Implicits.global val io: IO[BigInt] = computeF[IO](1, 4) io foreach { result => println(s"result = $result") } //=> 6227020800 65 / 87
  • 66. Reify F[_] : MonadF[_] : Monad with IOIO import scala.concurrent.ExecutionContext.Implicits.global val io: IO[BigInt] = computeF[IO](1, 4) io foreach { result => println(s"result = $result") } //=> 6227020800 Reify F[_] : MonadF[_] : Monad with cats.Idcats.Id val result: cats.Id[BigInt] = computeF[cats.Id](1, 4) println(s"result = $result") //=> 6227020800 66 / 87
  • 67. Reify F[_] : MonadF[_] : Monad with IOIO import scala.concurrent.ExecutionContext.Implicits.global val io: IO[BigInt] = computeF[IO](1, 4) io foreach { result => println(s"result = $result") } //=> 6227020800 Reify F[_] : MonadF[_] : Monad with cats.Idcats.Id val result: cats.Id[BigInt] = computeF[cats.Id](1, 4) println(s"result = $result") //=> 6227020800 Reify F[_] : MonadF[_] : Monad with OptionOption import cats.instances.option._ val maybeResult: Option[BigInt] = computeF[Option](1, 4) maybeResult foreach { result => println(s"result = $result") } //=> 6227020800 67 / 87
  • 68. Reify F[_] : MonadF[_] : Monad with IOIO import scala.concurrent.ExecutionContext.Implicits.global val io: IO[BigInt] = computeF[IO](1, 4) io foreach { result => println(s"result = $result") } //=> 6227020800 Reify F[_] : MonadF[_] : Monad with cats.Idcats.Id val result: cats.Id[BigInt] = computeF[cats.Id](1, 4) println(s"result = $result") //=> 6227020800 Reify F[_] : MonadF[_] : Monad with OptionOption import cats.instances.option._ val maybeResult: Option[BigInt] = computeF[Option](1, 4) maybeResult foreach { result => println(s"result = $result") } //=> 6227020800 Reify F[_] : MonadF[_] : Monad with FutureFuture import scala.concurrent.{Future, ExecutionContext} import ExecutionContext.Implicits.global import cats.instances.future._ val future: Future[BigInt] = computeF[Future](1, 4) future foreach { result => println(s"result = $result") } //=> 6227020800 68 / 87
  • 69. 16. IO.defer and IO.suspend object IO { // ADT sub types private case class Suspend[A](thunk: () => IO[A]) extends IO[A] { override def run(): A = thunk().run() } def suspend[A](ioa: => IO[A]): IO[A] = Suspend(() => ioa) def defer[A](ioa: => IO[A]): IO[A] = suspend(ioa) } 69 / 87
  • 70. 16. IO.defer and IO.suspend object IO { // ADT sub types private case class Suspend[A](thunk: () => IO[A]) extends IO[A] { override def run(): A = thunk().run() } def suspend[A](ioa: => IO[A]): IO[A] = Suspend(() => ioa) def defer[A](ioa: => IO[A]): IO[A] = suspend(ioa) } These methods defer the (possibly immediate) side effect of the inner IO. ADT sub type Suspend wraps another IO. 70 / 87
  • 71. Using IO.defer IO.pure(...) // without IO.defer val io1 = IO.pure { println("immediate side effect"); 5 } //=> immediate side effect Thread sleep 2000L io1 foreach println //=> 5 71 / 87
  • 72. Using IO.defer IO.pure(...) // without IO.defer val io1 = IO.pure { println("immediate side effect"); 5 } //=> immediate side effect Thread sleep 2000L io1 foreach println //=> 5 IO.defer(IO.pure(...)) val io2 = IO.defer { IO.pure { println("deferred side effect"); 5 } } Thread sleep 2000L io2 foreach println //=> deferred side effect //=> 5 72 / 87
  • 73. 17. IO.fromTry and IO.fromEither object IO { def fromTry[A](tryy: Try[A]): IO[A] = tryy.fold(IO.raiseError, IO.pure) def fromEither[A](either: Either[Throwable, A]): IO[A] = either.fold(IO.raiseError, IO.pure) } 73 / 87
  • 74. 17. IO.fromTry and IO.fromEither object IO { def fromTry[A](tryy: Try[A]): IO[A] = tryy.fold(IO.raiseError, IO.pure) def fromEither[A](either: Either[Throwable, A]): IO[A] = either.fold(IO.raiseError, IO.pure) } val tryy: Try[Seq[User]] = Try { User.getUsers } val io1: IO[Seq[User]] = IO.fromTry(tryy) val either: Either[Throwable, Seq[User]] = tryy.toEither val io2: IO[Seq[User]] = IO.fromEither(either) 74 / 87
  • 75. 18. IO.fromFuture object IO { // ADT sub types private case class FromFuture[A](fa: Future[A]) extends IO[A] { override def run(): A = Await.result(fa, Duration.Inf) // BLOCKING!!! } def fromFuture[A](future: Future[A]): IO[A] = FromFuture(future) } 75 / 87
  • 76. 18. IO.fromFuture object IO { // ADT sub types private case class FromFuture[A](fa: Future[A]) extends IO[A] { override def run(): A = Await.result(fa, Duration.Inf) // BLOCKING!!! } def fromFuture[A](future: Future[A]): IO[A] = FromFuture(future) } Attention: The implementation of fromFuture is a bit simplistic. Waiting for the Future to complete might block a thread! (A solution of this problem would require a redesign of my simple IO Monad, which doesn't support non-blocking async computations.) 76 / 87
  • 77. Using IO.fromFuture def futureGetUsers(implicit ec: ExecutionContext): Future[Seq[User]] = Future { println("side effect") User.getUsers } 77 / 87
  • 78. Using IO.fromFuture def futureGetUsers(implicit ec: ExecutionContext): Future[Seq[User]] = Future { println("side effect") User.getUsers } IO.fromFuture(f) is eager. implicit val ec: ExecutionContext = ExecutionContext.global val io = IO.fromFuture { futureGetUsers } io foreach { users => users foreach println } //=> "side effect" io foreach { users => users foreach println } 78 / 87
  • 79. Using IO.fromFuture def futureGetUsers(implicit ec: ExecutionContext): Future[Seq[User]] = Future { println("side effect") User.getUsers } IO.fromFuture(f) is eager. implicit val ec: ExecutionContext = ExecutionContext.global val io = IO.fromFuture { futureGetUsers } io foreach { users => users foreach println } //=> "side effect" io foreach { users => users foreach println } IO.defer(IO.fromFuture(f)) is lazy. implicit val ec: ExecutionContext = ExecutionContext.global val io = IO.defer { IO.fromFuture { futureGetUsers } } io foreach { users => users foreach println } //=> "side effect" io foreach { users => users foreach println } //=> "side effect" 79 / 87
  • 80. 19. IO.deferFuture object IO { def deferFuture[A](fa: => Future[A]): IO[A] = defer(IO.fromFuture(fa)) } 80 / 87
  • 81. 19. IO.deferFuture object IO { def deferFuture[A](fa: => Future[A]): IO[A] = defer(IO.fromFuture(fa)) } IO.deferFuture(f) is an alias for IO.defer(IO.fromFuture(f)). An ExecutionContext is still required to create the Future! 81 / 87
  • 82. Using IO.deferFuture def futureGetUsers(implicit ec: ExecutionContext): Future[Seq[User]] = Future { println("side effect") User.getUsers } 82 / 87
  • 83. Using IO.deferFuture def futureGetUsers(implicit ec: ExecutionContext): Future[Seq[User]] = Future { println("side effect") User.getUsers } IO.defer(IO.fromFuture(f)) is lazy. implicit val ec: ExecutionContext = ExecutionContext.global val io = IO.defer { IO.fromFuture { futureGetUsers } } io foreach { users => users foreach println } //=> "side effect" io foreach { users => users foreach println } //=> "side effect" 83 / 87
  • 84. Using IO.deferFuture def futureGetUsers(implicit ec: ExecutionContext): Future[Seq[User]] = Future { println("side effect") User.getUsers } IO.defer(IO.fromFuture(f)) is lazy. implicit val ec: ExecutionContext = ExecutionContext.global val io = IO.defer { IO.fromFuture { futureGetUsers } } io foreach { users => users foreach println } //=> "side effect" io foreach { users => users foreach println } //=> "side effect" IO.deferFuture(f) is a shortcut for that. implicit val ec: ExecutionContext = ExecutionContext.global val io = IO.deferFuture { futureGetUsers } io foreach { users => users foreach println } //=> "side effect" io foreach { users => users foreach println } //=> "side effect" 84 / 87
  • 86. Resources Code and Slides of this Talk: https://github.com/hermannhueck/implementing-io-monad Code and Slides for my Talk on: Future vs. Monix Task https://github.com/hermannhueck/future-vs-monix-task Monix Task 3.x Documentation (for comparison with IO) https://monix.io/docs/3x/eval/task.html Monix Task 3.x API Documentation (for comparison with IO) https://monix.io/api/3.0/monix/eval/Task.html Best Practice: "Should Not Block Threads" https://monix.io/docs/3x/best-practices/blocking.html What Referential Transparency can do for you Talk by Luka Jacobowitz at ScalaIO 2017 https://www.youtube.com/watch?v=X-cEGEJMx_4 86 / 87
  • 87. Thanks for Listening Q & A https://github.com/hermannhueck/implementing-io-monad 87 / 87