Taking your side effects aside
-
Upload
tomasz-kogut -
Category
Software
-
view
376 -
download
1
Transcript of Taking your side effects aside
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-effectsHere 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