TAKING YOUR
SIDE-EFFECTS ASIDE
About Me:
Software Engineer @
almendar
Scala since ~2011
Tomasz Kogut Warsaw
scala-poland
Target Audience
3
Spotting a side
effect
By brewing tea
5
6
7
8
f( , , ) =
9
f( , , ) =
10
f( , , ) =
11
f( , , ) =
12
Kettle problems
13
Kettle problems
14
val deliciousTeaFut: Future[ ] =
Future[ ].map{kettle => (...) }
15
val deliciousTeaFut: Future[ ] =
Future[ ].map{kettle => (...) }
(implicit ec:ExecutionContext)
16
Data like / Static / Lazy
Service like / Dynamic / Eager
17
Given impure A => B
it can be split into
pure A => C and
impure C => B with the needed side effect
18
19
f( , , ) =
20
Types of Kettles
• Databases
• RPC endpoints (e.g. REST)
• Console (i.e. println, readLine)
• Logging
• Filesystem
• External devices
21
Why not Future[ ]?
22
Pure
Core
Side-effects
23
Pure
Core
Side-effects
Here be
dragons!
24
Side effects are not bad
Uncontrolled side effects are bad
25
A => B
A => F[B]
26
A => B
A => F[B]
27
Capturing external
effects
A simple IO type
trait IO { def run: Unit }
29
trait IO { def run: Unit }
def PrintLine(msg: String): IO =
new IO { def run = println(msg) }
30
trait IO { def run: Unit }
def PrintLine(msg: String): IO =
new IO { def run = println(msg) }
def printBigger(a: Int, b: Int): IO = {
if(a < b) PrintLine(a.toString)
else PrintLine(b.toString)
}
31
trait IO { self =>
def run: Unit
def andThen(io: IO): IO = new IO {
def run = {self.run; io.run}
}
}
32
trait IO { self =>
def run: Unit
def andThen(io: IO): IO = new IO {
def run = {self.run; io.run}
}
}
33
trait IO { self =>
def run: Unit
def andThen(io: IO): IO = new IO {
def run = {self.run; io.run}
}
}
34
@ val polite = PrintLine("Hello") andThen PrintLine("Goodbye")
polite: IO = $sess.cmd0$IO$$anon$1@43f88150
@ polite.run
Hello
Goodbye
@
35
val cities = List("Warsaw", "Cracow", "Wroclaw")
val printCitiesList = cities.map(PrintLine)
val printAllCities: IO =
printCitiesList.foldLeft(IO.empty)(_ andThen _)
@ printAllCities.run
Warsaw
Cracow
Wroclaw
@
36
“Code is data”
37
What about Input?
38
trait IO[A] { self =>
def run: A
}
39
trait IO[A] { self =>
def run: A
def map[B](f: A => B): IO[B]
def flatMap[B](f: A => IO[B]): IO[B]
}
40
trait IO[A] { self =>
def run: A
def map[B](f: A => B): IO[B] =
new IO[B] { def run = f(self.run) }
def flatMap[B](f: A => IO[B]): IO[B] =
new IO[B] { def run = f(self.run).run }
}
41
trait IO[A] { self =>
def run: A
def map[B](f: A => B): IO[B] =
new IO[B] { def run = f(self.run) }
def flatMap[B](f: A => IO[B]): IO[B] =
new IO[B] { def run = f(self.run).run }
}
def ReadLine(): IO[String] = new IO[String] {
def run(): String = readLine
}
42
def ReadLine(): IO[String] = new IO[String] {
def run(): String = readLine}
def PrintLine(msg: String): IO[Unit] = new IO[Unit] {
def run = println(msg)
}
val enterNumberProgram =
for {
_ <- PrintLine("Enter a number:")
number <- ReadLine().map(_.toInt)
_ <- PrintLine(s"You entered $number")
} yield ()
43
val enterNumberProgram =
for {
_ <- PrintLine("Enter a number:")
number <- ReadLine().map(_.toInt)
_ <- PrintLine(s"You entered $number")
} yield ()
44
val enterNumberProgram =
for {
_ <- PrintLine("Enter a number:")
number <- ReadLine().map(_.toInt)
_ <- PrintLine(s"You entered $number")
} yield ()
@ enterNumberProgram.run
Enter a number:
234
You entered 234
45
val echoProgram = ReadLine.flatMap(PrintLine)
@ echoProgram.run
24
24
@
46
Let’s break the IO
47
def forever[A](io: IO[A]): IO[A] = {
lazy val t = forever(io)
io flatMap (_ => t)
}
48
def forever[A](io: IO[A]): IO[A] = {
lazy val t = forever(io)
io flatMap (_ => t)
}
@ val greetLikeMad = forever(PrintLine("Hello"))
greetLikeMad: IO[Unit] = $sess.cmd0$IO$$anon$2@31e5554e
@ greetLikeMad.run
49
def forever[A](io: IO[A]): IO[A] = {
lazy val t = forever(io)
io flatMap (_ => t)
}
@ val greetLikeMad = forever(PrintLine("Hello"))
greetLikeMad: IO[Unit] = $sess.cmd0$IO$$anon$2@31e5554e
@ greetLikeMad.run
Hello
Hello
(...)
Hello
java.lang.StackOverflowError
sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)
sun.nio.cs.UTF_8.access$200(UTF_8.java:57)
(...)
$sess.cmd0$IO$$anon$2.run(cmd0.sc:6)
$sess.cmd0$IO$$anon$2.run(cmd0.sc:6)
$sess.cmd0$IO$$anon$2.run(cmd0.sc:6) 50
def flatMap[B](f: A => IO[B]): IO[B] =
new IO[B] { def run = f(self.run).run }
51
“Code is data!!!”
52
sealed trait IO[A] { self =>
def flatMap[B](f: A ⇒ IO[B]): IO[B] = ???
}
53
sealed trait IO[A] { self =>
def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B]
54
sealed trait IO[A] { self =>
def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B]
55
sealed trait IO[A] { self =>
def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B]
56
sealed trait IO[A] { self =>
def map[B](f: A ⇒ B): IO[B] = ???
def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B]
57
sealed trait IO[A] { self =>
def map[B](f: A ⇒ B): IO[B] = ???
def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B]
case class Return[A](a: A) extends IO[A]
58
sealed trait IO[A] { self =>
def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_)))
def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B]
case class Return[A](a: A) extends IO[A]
59
sealed trait IO[A] { self =>
def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_)))
def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B]
case class Return[A](a: A) extends IO[A]
def PrintLine(s: String): IO[Unit] = ???
60
sealed trait IO[A] { self =>
def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_)))
def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B]
case class Return[A](a: A) extends IO[A]
def PrintLine(s: String): IO[Unit] = Return(println(s))
61
sealed trait IO[A] { self =>
def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_)))
def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B]
case class Return[A](a: A) extends IO[A]
def PrintLine(s: String): IO[Unit] = Return(println(s))
62
sealed trait IO[A] { self =>
def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_)))
def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B]
case class Return[A](a: A) extends IO[A]
def PrintLine(s: String): IO[Unit] = ???
63
sealed trait IO[A] { self =>
def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_)))
def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B]
case class Return[A](a: A) extends IO[A]
case class Suspend[A](resume: () ⇒ A) extends IO[A]
def PrintLine(s: String): IO[Unit] = ???
64
sealed trait IO[A] { self =>
def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_)))
def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B]
case class Return[A](a: A) extends IO[A]
case class Suspend[A](resume: () ⇒ A) extends IO[A]
def PrintLine(s: String): IO[Unit] = Suspend(() => println(s))
65
def PrintLine(s: String): IO[Unit] =
Suspend(() => Return(println(s)))
val p = IO.forever(PrintLine("...")) //this is flatMap
FlatMap(Suspend(() => println(s)), _ => FlatMap(Suspend(() =>
println(s)), _ => FlatMap(...)))
66
def PrintLine(s: String): IO[Unit] =
Suspend(() => Return(println(s)))
val p = IO.forever(PrintLine("...")) //this is flatMap
FlatMap(Suspend(() => println(s)), _ => FlatMap(Suspend(() =>
println(s)), _ => FlatMap(...)))
IO[A]
67
def PrintLine(s: String): IO[Unit] =
Suspend(() => Return(println(s)))
val p = IO.forever(PrintLine("...")) //this is flatMap
FlatMap(Suspend(() => println(s)), _ => FlatMap(Suspend(() =>
println(s)), _ => FlatMap(...)))
A => IO[B]
68
“Code is data”
69
“Code is data”
...but it has to run somewhere
70
final def run[A](io: IO[A]): A
71
final def run[A](io: IO[A]): A = io match {}
72
final def run[A](io: IO[A]): A = io match {
case Return(x) ⇒ x
}
73
final def run[A](io: IO[A]): A = io match {
case Return(x) ⇒ x
case Suspend(r) ⇒ r()
}
74
final def run[A](io: IO[A]): A = io match {
case Return(x) ⇒ x
case Suspend(r) ⇒ r()
case FlatMap(y, g) ⇒
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B])
75
final def run[A](io: IO[A]): A = io match {
case Return(x) ⇒ x
case Suspend(r) ⇒ r()
case FlatMap(y, g) ⇒ run(g(run(y)))
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B])
76
@tailrec
final def run[A](io: IO[A]): A = io match {
case Return(x) ⇒ x
case Suspend(r) ⇒ r()
case FlatMap(y, g) ⇒ run(g(run(y)))
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B])
77
@tailrec
final def run[A](io: IO[A]): A = io match {
case Return(x) ⇒ x
case Suspend(r) ⇒ r()
case FlatMap(y, g) ⇒
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B])
78
@tailrec
final def run[A](io: IO[A]): A = io match {
case Return(x) ⇒ x
case Suspend(r) ⇒ r()
case FlatMap(y, g) ⇒ y match {
case Return(a1) ⇒
case Suspend(r) ⇒
case FlatMap(y1, g1) ⇒
}
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B])
79
@tailrec
final def run[A](io: IO[A]): A = io match {
case Return(x) ⇒ x
case Suspend(r) ⇒ r()
case FlatMap(y, g) ⇒ y match {
case Return(a1) ⇒ run(g(a1))
case Suspend(r) ⇒ run(g(r()))
case FlatMap(y1, g1) ⇒
}
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B])
80
@tailrec
final def run[A](io: IO[A]): A = io match {
case Return(x) ⇒ x
case Suspend(r) ⇒ r()
case FlatMap(y, g) ⇒ y match {
case Return(a1) ⇒ run(g(a1))
case Suspend(r) ⇒ run(g(r()))
case FlatMap(y1, g1) ⇒ run(y1 flatMap g1 flatMap g)
}
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B])
81
@tailrec
final def run[A](io: IO[A]): A = io match {
case Return(x) ⇒ x
case Suspend(r) ⇒ r()
case FlatMap(y, g) ⇒ y match {
case Return(a1) ⇒ run(g(a1))
case Suspend(r) ⇒ run(g(r()))
case FlatMap(y1, g1) ⇒ run(y1 flatMap g1 flatMap g)
} //FlatMap(FlatMap(y1,g1), g)
}
case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B])
82
@tailrec
final def run[A](io: IO[A]): A = io match {
case Return(x) ⇒ x
case Suspend(r) ⇒ r()
case FlatMap(y, g) ⇒ y match {
case Return(a1) ⇒ run(g(a1))
case Suspend(r) ⇒ run(g(r()))
case FlatMap(y1, g1) ⇒
//run(y1 flatMap g1 flatMap g)
run(y1.flatMap(a ⇒ g1(a).flatMap(g)))
} // FlatMap(y1, a => FlatMap(g1(a), a1 => FlatMap...))
} 83
84
Trampolining
• Trade stack for heap
• Build a call tree
• It has a higher cost than a function call
• It’s a special case of a Free Monad
• type IO[A] = Free[() => A, A]
Let’s go Async
Getting dirty so you don’t have to
“Code is data”
86
case class Async[A](
register: ???
) extends IO[A]
87
case class Async[A](
register: (A => Unit) => Unit
) extends IO[A]
88
// def register[A](clb: A => Unit): Unit
case class Async[A](
register: (A => Unit) => Unit
) extends IO[A]
89
import scala.concurrent.ExecutionContext.global
def DoubleOnSeperateThread(d: Double) = Async[Double] {
onDone =>
{
global.execute { () =>
onDone(d * 2.0) //(A => Unit)}
}
}
val program = ReadLine
.map(_.toDouble)
.flatMap(x => DoubleOnSeperateThread(x))
.flatMap(y => PrintLine(y.toString))
IO.run(program)
90
@tailrec
final def run[A](io: IO[A]): A = io match {
(...)
case Async(register) => //(A => Unit) => Unit
val latch = new CountDownLatch(1)
var a: A = null.asInstanceOf[A]
register { a0 =>
a = a0
latch.countDown()
}
latch.await()
a 91
@tailrec
final def run[A](io: IO[A]): A = io match {
(...)
case Async(register) => //(A => Unit) => Unit
val latch = new CountDownLatch(1)
var a: A = null.asInstanceOf[A]
register { a0 => //(A => Unit), onDone
a = a0
latch.countDown()
}
latch.await()
a 92
Wrap up
• IO is usually called Task
• It can be used where you would have Future
• Open source implementations
• Monix
• FS2 (Functional Streams for Scala 2)
• Besides IO they also have streaming IO
93
Further reading/watching
• “Stackless Scala With Free Monads”, Rúnar Bjarnason (pdf)
• “Functional Async on the JVM”, λC Winter Retreat 2017, Daniel
Spiewak (youtube)
• “Functional Programming in Scala”, Paul Chiusano, Rúnar
Bjarnason (chapter 13, book)
• IO Inside, https://wiki.haskell.org/IO_inside
• https://github.com/functional-streams-for-scala/fs2
• https://github.com/monix/monix
94
Thank you!
95almendar Tomasz Kogut

Taking your side effects aside

  • 1.
  • 2.
    About Me: Software Engineer@ almendar Scala since ~2011 Tomasz Kogut Warsaw scala-poland
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
    f( , ,) = 9
  • 10.
    f( , ,) = 10
  • 11.
    f( , ,) = 11
  • 12.
    f( , ,) = 12
  • 13.
  • 14.
  • 15.
    val deliciousTeaFut: Future[] = Future[ ].map{kettle => (...) } 15
  • 16.
    val deliciousTeaFut: Future[] = Future[ ].map{kettle => (...) } (implicit ec:ExecutionContext) 16
  • 17.
    Data like /Static / Lazy Service like / Dynamic / Eager 17
  • 18.
    Given impure A=> B it can be split into pure A => C and impure C => B with the needed side effect 18
  • 19.
  • 20.
    f( , ,) = 20
  • 21.
    Types of Kettles •Databases • RPC endpoints (e.g. REST) • Console (i.e. println, readLine) • Logging • Filesystem • External devices 21
  • 22.
  • 23.
  • 24.
  • 25.
    Side effects arenot bad Uncontrolled side effects are bad 25
  • 26.
    A => B A=> F[B] 26
  • 27.
    A => B A=> F[B] 27
  • 28.
  • 29.
    trait IO {def run: Unit } 29
  • 30.
    trait IO {def run: Unit } def PrintLine(msg: String): IO = new IO { def run = println(msg) } 30
  • 31.
    trait IO {def run: Unit } def PrintLine(msg: String): IO = new IO { def run = println(msg) } def printBigger(a: Int, b: Int): IO = { if(a < b) PrintLine(a.toString) else PrintLine(b.toString) } 31
  • 32.
    trait IO {self => def run: Unit def andThen(io: IO): IO = new IO { def run = {self.run; io.run} } } 32
  • 33.
    trait IO {self => def run: Unit def andThen(io: IO): IO = new IO { def run = {self.run; io.run} } } 33
  • 34.
    trait IO {self => def run: Unit def andThen(io: IO): IO = new IO { def run = {self.run; io.run} } } 34
  • 35.
    @ val polite= PrintLine("Hello") andThen PrintLine("Goodbye") polite: IO = $sess.cmd0$IO$$anon$1@43f88150 @ polite.run Hello Goodbye @ 35
  • 36.
    val cities =List("Warsaw", "Cracow", "Wroclaw") val printCitiesList = cities.map(PrintLine) val printAllCities: IO = printCitiesList.foldLeft(IO.empty)(_ andThen _) @ printAllCities.run Warsaw Cracow Wroclaw @ 36
  • 37.
  • 38.
  • 39.
    trait IO[A] {self => def run: A } 39
  • 40.
    trait IO[A] {self => def run: A def map[B](f: A => B): IO[B] def flatMap[B](f: A => IO[B]): IO[B] } 40
  • 41.
    trait IO[A] {self => def run: A def map[B](f: A => B): IO[B] = new IO[B] { def run = f(self.run) } def flatMap[B](f: A => IO[B]): IO[B] = new IO[B] { def run = f(self.run).run } } 41
  • 42.
    trait IO[A] {self => def run: A def map[B](f: A => B): IO[B] = new IO[B] { def run = f(self.run) } def flatMap[B](f: A => IO[B]): IO[B] = new IO[B] { def run = f(self.run).run } } def ReadLine(): IO[String] = new IO[String] { def run(): String = readLine } 42
  • 43.
    def ReadLine(): IO[String]= new IO[String] { def run(): String = readLine} def PrintLine(msg: String): IO[Unit] = new IO[Unit] { def run = println(msg) } val enterNumberProgram = for { _ <- PrintLine("Enter a number:") number <- ReadLine().map(_.toInt) _ <- PrintLine(s"You entered $number") } yield () 43
  • 44.
    val enterNumberProgram = for{ _ <- PrintLine("Enter a number:") number <- ReadLine().map(_.toInt) _ <- PrintLine(s"You entered $number") } yield () 44
  • 45.
    val enterNumberProgram = for{ _ <- PrintLine("Enter a number:") number <- ReadLine().map(_.toInt) _ <- PrintLine(s"You entered $number") } yield () @ enterNumberProgram.run Enter a number: 234 You entered 234 45
  • 46.
    val echoProgram =ReadLine.flatMap(PrintLine) @ echoProgram.run 24 24 @ 46
  • 47.
  • 48.
    def forever[A](io: IO[A]):IO[A] = { lazy val t = forever(io) io flatMap (_ => t) } 48
  • 49.
    def forever[A](io: IO[A]):IO[A] = { lazy val t = forever(io) io flatMap (_ => t) } @ val greetLikeMad = forever(PrintLine("Hello")) greetLikeMad: IO[Unit] = $sess.cmd0$IO$$anon$2@31e5554e @ greetLikeMad.run 49
  • 50.
    def forever[A](io: IO[A]):IO[A] = { lazy val t = forever(io) io flatMap (_ => t) } @ val greetLikeMad = forever(PrintLine("Hello")) greetLikeMad: IO[Unit] = $sess.cmd0$IO$$anon$2@31e5554e @ greetLikeMad.run Hello Hello (...) Hello java.lang.StackOverflowError sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77) sun.nio.cs.UTF_8.access$200(UTF_8.java:57) (...) $sess.cmd0$IO$$anon$2.run(cmd0.sc:6) $sess.cmd0$IO$$anon$2.run(cmd0.sc:6) $sess.cmd0$IO$$anon$2.run(cmd0.sc:6) 50
  • 51.
    def flatMap[B](f: A=> IO[B]): IO[B] = new IO[B] { def run = f(self.run).run } 51
  • 52.
  • 53.
    sealed trait IO[A]{ self => def flatMap[B](f: A ⇒ IO[B]): IO[B] = ??? } 53
  • 54.
    sealed trait IO[A]{ self => def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B] 54
  • 55.
    sealed trait IO[A]{ self => def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B] 55
  • 56.
    sealed trait IO[A]{ self => def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B] 56
  • 57.
    sealed trait IO[A]{ self => def map[B](f: A ⇒ B): IO[B] = ??? def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B] 57
  • 58.
    sealed trait IO[A]{ self => def map[B](f: A ⇒ B): IO[B] = ??? def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B] case class Return[A](a: A) extends IO[A] 58
  • 59.
    sealed trait IO[A]{ self => def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_))) def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B] case class Return[A](a: A) extends IO[A] 59
  • 60.
    sealed trait IO[A]{ self => def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_))) def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B] case class Return[A](a: A) extends IO[A] def PrintLine(s: String): IO[Unit] = ??? 60
  • 61.
    sealed trait IO[A]{ self => def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_))) def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B] case class Return[A](a: A) extends IO[A] def PrintLine(s: String): IO[Unit] = Return(println(s)) 61
  • 62.
    sealed trait IO[A]{ self => def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_))) def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B] case class Return[A](a: A) extends IO[A] def PrintLine(s: String): IO[Unit] = Return(println(s)) 62
  • 63.
    sealed trait IO[A]{ self => def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_))) def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B] case class Return[A](a: A) extends IO[A] def PrintLine(s: String): IO[Unit] = ??? 63
  • 64.
    sealed trait IO[A]{ self => def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_))) def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B] case class Return[A](a: A) extends IO[A] case class Suspend[A](resume: () ⇒ A) extends IO[A] def PrintLine(s: String): IO[Unit] = ??? 64
  • 65.
    sealed trait IO[A]{ self => def map[B](f: A ⇒ B): IO[B] = flatMap(f andThen (Return(_))) def flatMap[B](f: A ⇒ IO[B]): IO[B] = FlatMap(this, f) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) extends IO[B] case class Return[A](a: A) extends IO[A] case class Suspend[A](resume: () ⇒ A) extends IO[A] def PrintLine(s: String): IO[Unit] = Suspend(() => println(s)) 65
  • 66.
    def PrintLine(s: String):IO[Unit] = Suspend(() => Return(println(s))) val p = IO.forever(PrintLine("...")) //this is flatMap FlatMap(Suspend(() => println(s)), _ => FlatMap(Suspend(() => println(s)), _ => FlatMap(...))) 66
  • 67.
    def PrintLine(s: String):IO[Unit] = Suspend(() => Return(println(s))) val p = IO.forever(PrintLine("...")) //this is flatMap FlatMap(Suspend(() => println(s)), _ => FlatMap(Suspend(() => println(s)), _ => FlatMap(...))) IO[A] 67
  • 68.
    def PrintLine(s: String):IO[Unit] = Suspend(() => Return(println(s))) val p = IO.forever(PrintLine("...")) //this is flatMap FlatMap(Suspend(() => println(s)), _ => FlatMap(Suspend(() => println(s)), _ => FlatMap(...))) A => IO[B] 68
  • 69.
  • 70.
    “Code is data” ...butit has to run somewhere 70
  • 71.
  • 72.
    final def run[A](io:IO[A]): A = io match {} 72
  • 73.
    final def run[A](io:IO[A]): A = io match { case Return(x) ⇒ x } 73
  • 74.
    final def run[A](io:IO[A]): A = io match { case Return(x) ⇒ x case Suspend(r) ⇒ r() } 74
  • 75.
    final def run[A](io:IO[A]): A = io match { case Return(x) ⇒ x case Suspend(r) ⇒ r() case FlatMap(y, g) ⇒ } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) 75
  • 76.
    final def run[A](io:IO[A]): A = io match { case Return(x) ⇒ x case Suspend(r) ⇒ r() case FlatMap(y, g) ⇒ run(g(run(y))) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) 76
  • 77.
    @tailrec final def run[A](io:IO[A]): A = io match { case Return(x) ⇒ x case Suspend(r) ⇒ r() case FlatMap(y, g) ⇒ run(g(run(y))) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) 77
  • 78.
    @tailrec final def run[A](io:IO[A]): A = io match { case Return(x) ⇒ x case Suspend(r) ⇒ r() case FlatMap(y, g) ⇒ } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) 78
  • 79.
    @tailrec final def run[A](io:IO[A]): A = io match { case Return(x) ⇒ x case Suspend(r) ⇒ r() case FlatMap(y, g) ⇒ y match { case Return(a1) ⇒ case Suspend(r) ⇒ case FlatMap(y1, g1) ⇒ } } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) 79
  • 80.
    @tailrec final def run[A](io:IO[A]): A = io match { case Return(x) ⇒ x case Suspend(r) ⇒ r() case FlatMap(y, g) ⇒ y match { case Return(a1) ⇒ run(g(a1)) case Suspend(r) ⇒ run(g(r())) case FlatMap(y1, g1) ⇒ } } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) 80
  • 81.
    @tailrec final def run[A](io:IO[A]): A = io match { case Return(x) ⇒ x case Suspend(r) ⇒ r() case FlatMap(y, g) ⇒ y match { case Return(a1) ⇒ run(g(a1)) case Suspend(r) ⇒ run(g(r())) case FlatMap(y1, g1) ⇒ run(y1 flatMap g1 flatMap g) } } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) 81
  • 82.
    @tailrec final def run[A](io:IO[A]): A = io match { case Return(x) ⇒ x case Suspend(r) ⇒ r() case FlatMap(y, g) ⇒ y match { case Return(a1) ⇒ run(g(a1)) case Suspend(r) ⇒ run(g(r())) case FlatMap(y1, g1) ⇒ run(y1 flatMap g1 flatMap g) } //FlatMap(FlatMap(y1,g1), g) } case class FlatMap[A, B](sub: IO[A], k: A ⇒ IO[B]) 82
  • 83.
    @tailrec final def run[A](io:IO[A]): A = io match { case Return(x) ⇒ x case Suspend(r) ⇒ r() case FlatMap(y, g) ⇒ y match { case Return(a1) ⇒ run(g(a1)) case Suspend(r) ⇒ run(g(r())) case FlatMap(y1, g1) ⇒ //run(y1 flatMap g1 flatMap g) run(y1.flatMap(a ⇒ g1(a).flatMap(g))) } // FlatMap(y1, a => FlatMap(g1(a), a1 => FlatMap...)) } 83
  • 84.
    84 Trampolining • Trade stackfor heap • Build a call tree • It has a higher cost than a function call • It’s a special case of a Free Monad • type IO[A] = Free[() => A, A]
  • 85.
    Let’s go Async Gettingdirty so you don’t have to
  • 86.
  • 87.
    case class Async[A]( register:??? ) extends IO[A] 87
  • 88.
    case class Async[A]( register:(A => Unit) => Unit ) extends IO[A] 88
  • 89.
    // def register[A](clb:A => Unit): Unit case class Async[A]( register: (A => Unit) => Unit ) extends IO[A] 89
  • 90.
    import scala.concurrent.ExecutionContext.global def DoubleOnSeperateThread(d:Double) = Async[Double] { onDone => { global.execute { () => onDone(d * 2.0) //(A => Unit)} } } val program = ReadLine .map(_.toDouble) .flatMap(x => DoubleOnSeperateThread(x)) .flatMap(y => PrintLine(y.toString)) IO.run(program) 90
  • 91.
    @tailrec final def run[A](io:IO[A]): A = io match { (...) case Async(register) => //(A => Unit) => Unit val latch = new CountDownLatch(1) var a: A = null.asInstanceOf[A] register { a0 => a = a0 latch.countDown() } latch.await() a 91
  • 92.
    @tailrec final def run[A](io:IO[A]): A = io match { (...) case Async(register) => //(A => Unit) => Unit val latch = new CountDownLatch(1) var a: A = null.asInstanceOf[A] register { a0 => //(A => Unit), onDone a = a0 latch.countDown() } latch.await() a 92
  • 93.
    Wrap up • IOis usually called Task • It can be used where you would have Future • Open source implementations • Monix • FS2 (Functional Streams for Scala 2) • Besides IO they also have streaming IO 93
  • 94.
    Further reading/watching • “StacklessScala With Free Monads”, Rúnar Bjarnason (pdf) • “Functional Async on the JVM”, λC Winter Retreat 2017, Daniel Spiewak (youtube) • “Functional Programming in Scala”, Paul Chiusano, Rúnar Bjarnason (chapter 13, book) • IO Inside, https://wiki.haskell.org/IO_inside • https://github.com/functional-streams-for-scala/fs2 • https://github.com/monix/monix 94
  • 95.

Editor's Notes

  • #4 Spectrum of devs Java adapt for benefit
  • #6 Declicouse beveraga
  • #9 Presentation not about brewing tea but coding
  • #12 First two arguments are different leave them on a table, be back in 10 minutes
  • #13 next: broken cable
  • #14 bite through cat
  • #15  kettle is fine, external dep is down power outage power plant
  • #16 What I would expect to see typical and often Future spreads over you code
  • #17 next: Dynamic / Lazy Polluting signatures
  • #18 next: Imputer = pure + impure
  • #21 We now don’t care where from the waters comes, and it’s easier to test that way, testing
  • #22 Based on time!
  • #23 next: circle Eager evaluation Depends on executionContext
  • #24 Side-effect must be captured and released Dark, grim place
  • #25 next: Uncontrolled side-effect are bad
  • #26 next: Simple functions Calling side effect bad is like calling life a lethal disease
  • #27 F can be an option, try, etiehr or sth else, it also can help us to control IO. As we already saw Future is not good.
  • #29 IO comes from Haskell
  • #30 next: PrintLine Example
  • #31 next: if example
  • #32 next: andThen
  • #33 composition is important here it starts to get interesting next: highlights
  • #36 next: List of cities
  • #37 next: Code is Data
  • #40 next: map and flatMap
  • #42 next: ReadLine example
  • #46 next: echo program
  • #47 We controll side-effect, run at will
  • #48 next: forever
  • #51 Stackoverflow is a Fatal Error Future avoids this because it submits it to EC Crossing asynchronouse-boundary
  • #55 next: highlights
  • #62 next: strike through
  • #67 next: highlights
  • #69 FlatMap is like Stream a head that is a frozen computation and tail that will be computed later
  • #70 next: must run somewhere
  • #76 next: naive implementation
  • #83 Monads laws to the rescue not only to oppress you and bore you
  • #84 Associativity law
  • #92 next: highlight