95
TAKING YOUR SIDE-EFFECTS ASIDE

Taking your side effects aside

Embed Size (px)

Citation preview

Page 1: Taking your side effects aside

TAKING YOUR

SIDE-EFFECTS ASIDE

Page 2: Taking your side effects aside

About Me:

Software Engineer @

almendar

Scala since ~2011

Tomasz Kogut Warsaw

scala-poland

Page 3: Taking your side effects aside

Target Audience

3

Page 4: Taking your side effects aside

Spotting a side

effect

By brewing tea

Page 5: Taking your side effects aside

5

Page 6: Taking your side effects aside

6

Page 7: Taking your side effects aside

7

Page 8: Taking your side effects aside

8

Page 9: Taking your side effects aside

f( , , ) =

9

Page 10: Taking your side effects aside

f( , , ) =

10

Page 11: Taking your side effects aside

f( , , ) =

11

Page 12: Taking your side effects aside

f( , , ) =

12

Page 13: Taking your side effects aside

Kettle problems

13

Page 14: Taking your side effects aside

Kettle problems

14

Page 15: Taking your side effects aside

val deliciousTeaFut: Future[ ] =

Future[ ].map{kettle => (...) }

15

Page 16: Taking your side effects aside

val deliciousTeaFut: Future[ ] =

Future[ ].map{kettle => (...) }

(implicit ec:ExecutionContext)

16

Page 17: Taking your side effects aside

Data like / Static / Lazy

Service like / Dynamic / Eager

17

Page 18: Taking your side effects aside

Given impure A => B

it can be split into

pure A => C and

impure C => B with the needed side effect

18

Page 19: Taking your side effects aside

19

Page 20: Taking your side effects aside

f( , , ) = 20

Page 21: Taking your side effects aside

Types of Kettles

• Databases

• RPC endpoints (e.g. REST)

• Console (i.e. println, readLine)

• Logging

• Filesystem

• External devices

21

Page 22: Taking your side effects aside

Why not Future[ ]?

22

Page 23: Taking your side effects aside

Pure

Core

Side-effects

23

Page 24: Taking your side effects aside

Pure

Core

Side-effectsHere be

dragons!

24

Page 25: Taking your side effects aside

Side effects are not bad

Uncontrolled side effects are bad

25

Page 26: Taking your side effects aside

A => B

A => F[B]

26

Page 27: Taking your side effects aside

A => B

A => F[B]

27

Page 28: Taking your side effects aside

Capturing external

effects

A simple IO type

Page 29: Taking your side effects aside

trait IO { def run: Unit }

29

Page 30: Taking your side effects aside

trait IO { def run: Unit }

def PrintLine(msg: String): IO =

new IO { def run = println(msg) }

30

Page 31: Taking your side effects aside

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

Page 32: Taking your side effects aside

trait IO { self =>

def run: Unit

def andThen(io: IO): IO = new IO {

def run = {self.run; io.run}

}

}

32

Page 33: Taking your side effects aside

trait IO { self =>

def run: Unit

def andThen(io: IO): IO = new IO {

def run = {self.run; io.run}

}

}

33

Page 34: Taking your side effects aside

trait IO { self =>

def run: Unit

def andThen(io: IO): IO = new IO {

def run = {self.run; io.run}

}

}

34

Page 35: Taking your side effects aside

@ val polite = PrintLine("Hello") andThen PrintLine("Goodbye")

polite: IO = $sess.cmd0$IO$$anon$1@43f88150

@ polite.run

Hello

Goodbye

@

35

Page 36: Taking your side effects aside

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

Page 37: Taking your side effects aside

“Code is data”

37

Page 38: Taking your side effects aside

What about Input?

38

Page 39: Taking your side effects aside

trait IO[A] { self =>

def run: A

}

39

Page 40: Taking your side effects aside

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

Page 41: Taking your side effects aside

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

Page 42: Taking your side effects aside

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

Page 43: Taking your side effects aside

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

Page 44: Taking your side effects aside

val enterNumberProgram =

for {

_ <- PrintLine("Enter a number:")

number <- ReadLine().map(_.toInt)

_ <- PrintLine(s"You entered $number")

} yield ()

44

Page 45: Taking your side effects aside

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

Page 46: Taking your side effects aside

val echoProgram = ReadLine.flatMap(PrintLine)

@ echoProgram.run

24

24

@

46

Page 47: Taking your side effects aside

Let’s break the IO

47

Page 48: Taking your side effects aside

def forever[A](io: IO[A]): IO[A] = {

lazy val t = forever(io)

io flatMap (_ => t)

}

48

Page 49: Taking your side effects aside

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

Page 50: Taking your side effects aside

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

Page 51: Taking your side effects aside

def flatMap[B](f: A => IO[B]): IO[B] =

new IO[B] { def run = f(self.run).run }

51

Page 52: Taking your side effects aside

“Code is data!!!”

52

Page 53: Taking your side effects aside

sealed trait IO[A] { self =>

def flatMap[B](f: A ⇒ IO[B]): IO[B] = ???

}

53

Page 54: Taking your side effects aside

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

Page 55: Taking your side effects aside

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

Page 56: Taking your side effects aside

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

Page 57: Taking your side effects aside

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

Page 58: Taking your side effects aside

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

Page 59: Taking your side effects aside

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

Page 60: Taking your side effects aside

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

Page 61: Taking your side effects aside

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

Page 62: Taking your side effects aside

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

Page 63: Taking your side effects aside

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

Page 64: Taking your side effects aside

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

Page 65: Taking your side effects aside

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

Page 66: Taking your side effects aside

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

Page 67: Taking your side effects aside

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

Page 68: Taking your side effects aside

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

Page 69: Taking your side effects aside

“Code is data”

69

Page 70: Taking your side effects aside

“Code is data”

...but it has to run somewhere

70

Page 71: Taking your side effects aside

final def run[A](io: IO[A]): A

71

Page 72: Taking your side effects aside

final def run[A](io: IO[A]): A = io match {}

72

Page 73: Taking your side effects aside

final def run[A](io: IO[A]): A = io match {

case Return(x) ⇒ x

}

73

Page 74: Taking your side effects aside

final def run[A](io: IO[A]): A = io match {

case Return(x) ⇒ x

case Suspend(r) ⇒ r()

}

74

Page 75: Taking your side effects aside

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

Page 76: Taking your side effects aside

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

Page 77: Taking your side effects aside

@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

Page 78: Taking your side effects aside

@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

Page 79: Taking your side effects aside

@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

Page 80: Taking your side effects aside

@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

Page 81: Taking your side effects aside

@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

Page 82: Taking your side effects aside

@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

Page 83: Taking your side effects aside

@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

Page 84: Taking your side effects aside

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]

Page 85: Taking your side effects aside

Let’s go Async

Getting dirty so you don’t have to

Page 86: Taking your side effects aside

“Code is data”

86

Page 87: Taking your side effects aside

case class Async[A](

register: ???

) extends IO[A]

87

Page 88: Taking your side effects aside

case class Async[A](

register: (A => Unit) => Unit

) extends IO[A]

88

Page 89: Taking your side effects aside

// def register[A](clb: A => Unit): Unit

case class Async[A](

register: (A => Unit) => Unit

) extends IO[A]

89

Page 90: Taking your side effects aside

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

Page 91: Taking your side effects aside

@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

Page 92: Taking your side effects aside

@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

Page 93: Taking your side effects aside

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

Page 94: Taking your side effects aside

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

Page 95: Taking your side effects aside

Thank you!

95almendar Tomasz Kogut