SlideShare a Scribd company logo
1 of 88
Download to read offline
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

More Related Content

What's hot

Composing an App with Free Monads (using Cats)
Composing an App with Free Monads (using Cats)Composing an App with Free Monads (using Cats)
Composing an App with Free Monads (using Cats)Hermann Hueck
 
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
 
Designing with Capabilities
Designing with CapabilitiesDesigning with Capabilities
Designing with CapabilitiesScott Wlaschin
 
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
 
A Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIOA Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIOJorge Vásquez
 
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
 
Functor, Apply, Applicative And Monad
Functor, Apply, Applicative And MonadFunctor, Apply, Applicative And Monad
Functor, Apply, Applicative And MonadOliver Daff
 
Monoids - Part 1 - with examples using Scalaz and Cats
Monoids - Part 1 - with examples using Scalaz and CatsMonoids - Part 1 - with examples using Scalaz and Cats
Monoids - Part 1 - with examples using Scalaz and CatsPhilip Schwarz
 
Purely Functional Data Structures in Scala
Purely Functional Data Structures in ScalaPurely Functional Data Structures in Scala
Purely Functional Data Structures in ScalaVladimir Kostyukov
 
ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022Alexander Ioffe
 
Computer Graphics in Java and Scala - Part 1
Computer Graphics in Java and Scala - Part 1Computer Graphics in Java and Scala - Part 1
Computer Graphics in Java and Scala - Part 1Philip Schwarz
 
Railway Oriented Programming
Railway Oriented ProgrammingRailway Oriented Programming
Railway Oriented ProgrammingScott Wlaschin
 
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...Philip Schwarz
 
Functional Programming Patterns (NDC London 2014)
Functional Programming Patterns (NDC London 2014)Functional Programming Patterns (NDC London 2014)
Functional Programming Patterns (NDC London 2014)Scott Wlaschin
 
Utilizing kotlin flows in an android application
Utilizing kotlin flows in an android applicationUtilizing kotlin flows in an android application
Utilizing kotlin flows in an android applicationSeven Peaks Speaks
 
Morel, a Functional Query Language
Morel, a Functional Query LanguageMorel, a Functional Query Language
Morel, a Functional Query LanguageJulian Hyde
 

What's hot (20)

Zio in real world
Zio in real worldZio in real world
Zio in real world
 
Composing an App with Free Monads (using Cats)
Composing an App with Free Monads (using Cats)Composing an App with Free Monads (using Cats)
Composing an App with Free Monads (using Cats)
 
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
 
Designing with Capabilities
Designing with CapabilitiesDesigning with Capabilities
Designing with Capabilities
 
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
 
A Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIOA Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIO
 
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
 
Functor, Apply, Applicative And Monad
Functor, Apply, Applicative And MonadFunctor, Apply, Applicative And Monad
Functor, Apply, Applicative And Monad
 
Monoids - Part 1 - with examples using Scalaz and Cats
Monoids - Part 1 - with examples using Scalaz and CatsMonoids - Part 1 - with examples using Scalaz and Cats
Monoids - Part 1 - with examples using Scalaz and Cats
 
Purely Functional Data Structures in Scala
Purely Functional Data Structures in ScalaPurely Functional Data Structures in Scala
Purely Functional Data Structures in Scala
 
ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022ZIO-Direct - Functional Scala 2022
ZIO-Direct - Functional Scala 2022
 
Computer Graphics in Java and Scala - Part 1
Computer Graphics in Java and Scala - Part 1Computer Graphics in Java and Scala - Part 1
Computer Graphics in Java and Scala - Part 1
 
Railway Oriented Programming
Railway Oriented ProgrammingRailway Oriented Programming
Railway Oriented Programming
 
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
N-Queens Combinatorial Problem - Polyglot FP for Fun and Profit – Haskell and...
 
Functional Programming Patterns (NDC London 2014)
Functional Programming Patterns (NDC London 2014)Functional Programming Patterns (NDC London 2014)
Functional Programming Patterns (NDC London 2014)
 
Monads do not Compose
Monads do not ComposeMonads do not Compose
Monads do not Compose
 
OOP and FP
OOP and FPOOP and FP
OOP and FP
 
Utilizing kotlin flows in an android application
Utilizing kotlin flows in an android applicationUtilizing kotlin flows in an android application
Utilizing kotlin flows in an android application
 
Morel, a Functional Query Language
Morel, a Functional Query LanguageMorel, a Functional Query Language
Morel, a Functional Query Language
 

Similar to 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
 
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
 
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
 

Similar to 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
 
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
 
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
 

More from 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
 

More from 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
 

Recently uploaded

Sales Territory Management: A Definitive Guide to Expand Sales Coverage
Sales Territory Management: A Definitive Guide to Expand Sales CoverageSales Territory Management: A Definitive Guide to Expand Sales Coverage
Sales Territory Management: A Definitive Guide to Expand Sales CoverageDista
 
Generative AI for Cybersecurity - EC-Council
Generative AI for Cybersecurity - EC-CouncilGenerative AI for Cybersecurity - EC-Council
Generative AI for Cybersecurity - EC-CouncilVICTOR MAESTRE RAMIREZ
 
Top Software Development Trends in 2024
Top Software Development Trends in  2024Top Software Development Trends in  2024
Top Software Development Trends in 2024Mind IT Systems
 
Growing Oxen: channel operators and retries
Growing Oxen: channel operators and retriesGrowing Oxen: channel operators and retries
Growing Oxen: channel operators and retriesSoftwareMill
 
Your Vision, Our Expertise: TECUNIQUE's Tailored Software Teams
Your Vision, Our Expertise: TECUNIQUE's Tailored Software TeamsYour Vision, Our Expertise: TECUNIQUE's Tailored Software Teams
Your Vision, Our Expertise: TECUNIQUE's Tailored Software TeamsJaydeep Chhasatia
 
20240319 Car Simulator Plan.pptx . Plan for a JavaScript Car Driving Simulator.
20240319 Car Simulator Plan.pptx . Plan for a JavaScript Car Driving Simulator.20240319 Car Simulator Plan.pptx . Plan for a JavaScript Car Driving Simulator.
20240319 Car Simulator Plan.pptx . Plan for a JavaScript Car Driving Simulator.Sharon Liu
 
IA Generativa y Grafos de Neo4j: RAG time
IA Generativa y Grafos de Neo4j: RAG timeIA Generativa y Grafos de Neo4j: RAG time
IA Generativa y Grafos de Neo4j: RAG timeNeo4j
 
Enterprise Document Management System - Qualityze Inc
Enterprise Document Management System - Qualityze IncEnterprise Document Management System - Qualityze Inc
Enterprise Document Management System - Qualityze Incrobinwilliams8624
 
AI Embracing Every Shade of Human Beauty
AI Embracing Every Shade of Human BeautyAI Embracing Every Shade of Human Beauty
AI Embracing Every Shade of Human BeautyRaymond Okyere-Forson
 
Optimizing Business Potential: A Guide to Outsourcing Engineering Services in...
Optimizing Business Potential: A Guide to Outsourcing Engineering Services in...Optimizing Business Potential: A Guide to Outsourcing Engineering Services in...
Optimizing Business Potential: A Guide to Outsourcing Engineering Services in...Jaydeep Chhasatia
 
eAuditor Audits & Inspections - conduct field inspections
eAuditor Audits & Inspections - conduct field inspectionseAuditor Audits & Inspections - conduct field inspections
eAuditor Audits & Inspections - conduct field inspectionsNirav Modi
 
How Does the Epitome of Spyware Differ from Other Malicious Software?
How Does the Epitome of Spyware Differ from Other Malicious Software?How Does the Epitome of Spyware Differ from Other Malicious Software?
How Does the Epitome of Spyware Differ from Other Malicious Software?AmeliaSmith90
 
Kawika Technologies pvt ltd Software Development Company in Trivandrum
Kawika Technologies pvt ltd Software Development Company in TrivandrumKawika Technologies pvt ltd Software Development Company in Trivandrum
Kawika Technologies pvt ltd Software Development Company in TrivandrumKawika Technologies
 
online pdf editor software solutions.pdf
online pdf editor software solutions.pdfonline pdf editor software solutions.pdf
online pdf editor software solutions.pdfMeon Technology
 
Why Choose Brain Inventory For Ecommerce Development.pdf
Why Choose Brain Inventory For Ecommerce Development.pdfWhy Choose Brain Inventory For Ecommerce Development.pdf
Why Choose Brain Inventory For Ecommerce Development.pdfBrain Inventory
 
Streamlining Your Application Builds with Cloud Native Buildpacks
Streamlining Your Application Builds  with Cloud Native BuildpacksStreamlining Your Application Builds  with Cloud Native Buildpacks
Streamlining Your Application Builds with Cloud Native BuildpacksVish Abrams
 
ARM Talk @ Rejekts - Will ARM be the new Mainstream in our Data Centers_.pdf
ARM Talk @ Rejekts - Will ARM be the new Mainstream in our Data Centers_.pdfARM Talk @ Rejekts - Will ARM be the new Mainstream in our Data Centers_.pdf
ARM Talk @ Rejekts - Will ARM be the new Mainstream in our Data Centers_.pdfTobias Schneck
 
Cybersecurity Challenges with Generative AI - for Good and Bad
Cybersecurity Challenges with Generative AI - for Good and BadCybersecurity Challenges with Generative AI - for Good and Bad
Cybersecurity Challenges with Generative AI - for Good and BadIvo Andreev
 
JS-Experts - Cybersecurity for Generative AI
JS-Experts - Cybersecurity for Generative AIJS-Experts - Cybersecurity for Generative AI
JS-Experts - Cybersecurity for Generative AIIvo Andreev
 

Recently uploaded (20)

Sales Territory Management: A Definitive Guide to Expand Sales Coverage
Sales Territory Management: A Definitive Guide to Expand Sales CoverageSales Territory Management: A Definitive Guide to Expand Sales Coverage
Sales Territory Management: A Definitive Guide to Expand Sales Coverage
 
Salesforce AI Associate Certification.pptx
Salesforce AI Associate Certification.pptxSalesforce AI Associate Certification.pptx
Salesforce AI Associate Certification.pptx
 
Generative AI for Cybersecurity - EC-Council
Generative AI for Cybersecurity - EC-CouncilGenerative AI for Cybersecurity - EC-Council
Generative AI for Cybersecurity - EC-Council
 
Top Software Development Trends in 2024
Top Software Development Trends in  2024Top Software Development Trends in  2024
Top Software Development Trends in 2024
 
Growing Oxen: channel operators and retries
Growing Oxen: channel operators and retriesGrowing Oxen: channel operators and retries
Growing Oxen: channel operators and retries
 
Your Vision, Our Expertise: TECUNIQUE's Tailored Software Teams
Your Vision, Our Expertise: TECUNIQUE's Tailored Software TeamsYour Vision, Our Expertise: TECUNIQUE's Tailored Software Teams
Your Vision, Our Expertise: TECUNIQUE's Tailored Software Teams
 
20240319 Car Simulator Plan.pptx . Plan for a JavaScript Car Driving Simulator.
20240319 Car Simulator Plan.pptx . Plan for a JavaScript Car Driving Simulator.20240319 Car Simulator Plan.pptx . Plan for a JavaScript Car Driving Simulator.
20240319 Car Simulator Plan.pptx . Plan for a JavaScript Car Driving Simulator.
 
IA Generativa y Grafos de Neo4j: RAG time
IA Generativa y Grafos de Neo4j: RAG timeIA Generativa y Grafos de Neo4j: RAG time
IA Generativa y Grafos de Neo4j: RAG time
 
Enterprise Document Management System - Qualityze Inc
Enterprise Document Management System - Qualityze IncEnterprise Document Management System - Qualityze Inc
Enterprise Document Management System - Qualityze Inc
 
AI Embracing Every Shade of Human Beauty
AI Embracing Every Shade of Human BeautyAI Embracing Every Shade of Human Beauty
AI Embracing Every Shade of Human Beauty
 
Optimizing Business Potential: A Guide to Outsourcing Engineering Services in...
Optimizing Business Potential: A Guide to Outsourcing Engineering Services in...Optimizing Business Potential: A Guide to Outsourcing Engineering Services in...
Optimizing Business Potential: A Guide to Outsourcing Engineering Services in...
 
eAuditor Audits & Inspections - conduct field inspections
eAuditor Audits & Inspections - conduct field inspectionseAuditor Audits & Inspections - conduct field inspections
eAuditor Audits & Inspections - conduct field inspections
 
How Does the Epitome of Spyware Differ from Other Malicious Software?
How Does the Epitome of Spyware Differ from Other Malicious Software?How Does the Epitome of Spyware Differ from Other Malicious Software?
How Does the Epitome of Spyware Differ from Other Malicious Software?
 
Kawika Technologies pvt ltd Software Development Company in Trivandrum
Kawika Technologies pvt ltd Software Development Company in TrivandrumKawika Technologies pvt ltd Software Development Company in Trivandrum
Kawika Technologies pvt ltd Software Development Company in Trivandrum
 
online pdf editor software solutions.pdf
online pdf editor software solutions.pdfonline pdf editor software solutions.pdf
online pdf editor software solutions.pdf
 
Why Choose Brain Inventory For Ecommerce Development.pdf
Why Choose Brain Inventory For Ecommerce Development.pdfWhy Choose Brain Inventory For Ecommerce Development.pdf
Why Choose Brain Inventory For Ecommerce Development.pdf
 
Streamlining Your Application Builds with Cloud Native Buildpacks
Streamlining Your Application Builds  with Cloud Native BuildpacksStreamlining Your Application Builds  with Cloud Native Buildpacks
Streamlining Your Application Builds with Cloud Native Buildpacks
 
ARM Talk @ Rejekts - Will ARM be the new Mainstream in our Data Centers_.pdf
ARM Talk @ Rejekts - Will ARM be the new Mainstream in our Data Centers_.pdfARM Talk @ Rejekts - Will ARM be the new Mainstream in our Data Centers_.pdf
ARM Talk @ Rejekts - Will ARM be the new Mainstream in our Data Centers_.pdf
 
Cybersecurity Challenges with Generative AI - for Good and Bad
Cybersecurity Challenges with Generative AI - for Good and BadCybersecurity Challenges with Generative AI - for Good and Bad
Cybersecurity Challenges with Generative AI - for Good and Bad
 
JS-Experts - Cybersecurity for Generative AI
JS-Experts - Cybersecurity for Generative AIJS-Experts - Cybersecurity for Generative AI
JS-Experts - Cybersecurity for Generative AI
 

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