Successfully reported this slideshow.
Upcoming SlideShare
×

Slides from my conf at scala.io

• Full Name
Comment goes here.

Are you sure you want to Yes No

1. 1. Playing with the State Monad David Galichet Freelance Developer Twitter : @dgalichet
2. 2. Let’s start with a simple problem 3 2 1 0 0 1 2 3
3. 3. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 0 (A, R, A, L, A, A) 2 Score : 0 1 0 0 1 2 3
4. 4. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 1 (A, R, A, L, A, A) 2 Score : 0 1 0 0 1 2 3
5. 5. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 1 (A, R, A, L, A, A) 2 Score : 0 1 0 0 1 2 3
6. 6. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 1 (A, R, A, L, A, A) 2 Score : 0 1 0 0 1 2 3
7. 7. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 1 (A, R, A, L, A, A) 2 Score : 0 1 0 0 1 2 3
8. 8. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 1 (A, R, A, L, A, A) 2 Score : 0 1 0 0 1 2 3
9. 9. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 1 (A, R, A, L, A, A) 2 Score : 1 1 0 0 1 2 3
10. 10. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 2 (A, R, A, L, A, A) 2 Score : 1 1 0 0 1 2 3
11. 11. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 2 (A, R, A, L, A, A) 2 Score : 1 1 0 0 1 2 3
12. 12. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 2 (A, R, A, L, A, A) 2 Score : 1 1 0 R1 is blocked by R2 ! 0 1 2 3
13. 13. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 2 (A, R, A, L, A, A) 2 Score : 1 1 0 0 1 2 3
14. 14. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 2 (A, R, A, L, A, A) 2 Score : 1 1 0 0 1 2 3
15. 15. A simple simulation R1 R2 3 (A, A, R, A, A, A) Score : 2 (A, R, A, L, A, A) 2 Score : 1 1 0 0 1 2 3
16. 16. The game rules • We want to simulate two robots moving through a nxm playground • Each robot can either turn on a direction (North, South, East, West) or move one step forward • Robots move or turn according to instructions
17. 17. The game rules • A robot can’t go out of the playground • A robot will be blocked if another robot is on the place • Some coins are spread on the playground • Robots gather coins when they move over it
18. 18. Think about this game • It appears that we will deal with many states : • Playground with its coins • Robots with their positions and gathered coins
19. 19. We want functional purity • Functional Purity has many advantages like composability, idempotence, maintainability and thread safety • We need to ﬁnd a way to deal with states and remain pure
20. 20. Dealing with states S => (S, A) • S is the type of a state and A the type of a computation • The outcome of this function is a new state and a result
21. 21. Chaining states computations def chainStOps(! c1: S => (S, A), ! c2: S => (S, A)! ): S => (S, A) = { s =>! val (s1, _) = c1(s)! c2(s1)! } Repeated many times, this can be error prone !
22. 22. Introducing State Monad • The aim of the state monad is to abstract over state manipulations
23. 23. Introducing State Monad trait State[S, +A] {! def run(initial: S): (S, A)! def map[B](f: A => B): State[S, B] = ???! def flatMap[B](f: A => State[S, B]): State[S, B] = ???! }! ! object State {! def apply[S, A](f: S => (S, A)): State[S, A] = ???! }
24. 24. Introducing State Monad trait State[S, +A] {! def run(initial: S): (S, A)! def map[B](f: A => B): State[S, B] = ???! def flatMap[B](f: A => State[S, B]): State[S, B] = ???! }! ! object State {! def apply[S, A](f: S => (S, A)): State[S, A] = ! new State[S, A] {! def run(initial: S): (S, A) = f(initial)! }! } State Monad embed computation !
25. 25. Introducing State Monad trait State[S, +A] {! def run(initial: S): (S, A)! ! def map[B](f: A => B): State[S, B] = State { s =>! val (s1, a) = run(s)! (s1, f(a))! }! Don’t forget the definition: ! State.apply(S => (S, A)): State[S,A] ! ! def flatMap[B](f: A => State[S, B]): State[S, B] = ???! }
26. 26. Introducing State Monad trait State[S, +A] {! def run(initial: S): (S, A)! ! def map[B](f: A => B): State[S, B] = State { s =>! val (s1, a) = run(s)! (s1, f(a))! }! Don’t forget the definition: ! State.apply(S => (S, A)): State[S,A] ! ! def flatMap[B](f: A => State[S, B]): State[S, B] = State { s =>! val (s1, a) = run(s)! f(a).run(s1)! }! }
27. 27. Coming back to our game ! • We drive robots using a list of instructions sealed trait Instruction! case object L extends Instruction // turn Left! case object R extends Instruction // turn Right! case object A extends Instruction // Go on
28. 28. Coming back to our game ! • Each robot has a direction sealed trait Direction {! def turn(i: Instruction): Direction! }! case object North extends Direction {! def turn(i: Instruction) = i match {! case L => West! case R => East! case _ => this! }! }! case object South extends Direction { ... }! case object East extends Direction { ... }! case object West extends Direction { ... }
29. 29. Coming back to our game ! • A direction and a location deﬁne a position case class Point(x: Int, y: Int)! ! case class Position(point: Point, dir: Direction) {! def move(s: Playground): Position = {! val p1 = dir match {! case North => copy(point = point.copy(y = point.y + 1))! case South => ...! }! if (s.isPossiblePosition(p1)) p1 else this! }! def turn(instruction: Instruction): Position = ! copy(direction = direction.turn(instruction))! }
30. 30. Coming back to our game ! • And each Robot is a player with a Score sealed trait Player! case object R1 extends Player! case object R2 extends Player! ! case class Score(player: Player, score: Int)
31. 31. Coming back to our game ! • The state of each Robot is deﬁned as : case class Robot(! player: Player, ! positions: List[Position], ! coins: List[Point] = Nil) {! lazy val currentPosition = positions.head! ! ! lazy val score = Score(player, coins.size)! def addPosition(next: Position) = copy(positions = next::positions)! ! def addCoin(coin: Point) = copy(coins = coin::coins)! }
32. 32. Coming back to our game ! • Robots evolve in a playground : case class Playground(! bottomLeft: Point, topRight: Point, ! coins: Set[Point],! r1: Robot, r2: Robot) {! ! ! ! ! def isInPlayground(point: Point): Boolean =! bottomLeft.x <= point.x && ...! def isPossiblePosition(pos: Position): Boolean = ...! lazy val scores = (r1.score, r2.score)! def swapRobots(): Playground = copy(r1 = r2, r2 = r1)! }
33. 33. Look at what we did • a set of Instructions, • a Position composed with Points and Direction, • a deﬁnition for Players and Score, • a way to deﬁne Robot state • and a way to deﬁne Playground state
34. 34. Let put these all together ! • Now, we need a method to process a single instruction • And a method to process all instructions • The expected result is a State Monad that will be run with the initial state of the playground
35. 35. Processing a single instruction def processInstruction(i: Instruction)(s: Playground): Playground = {! val next = i match {! case A => s.r1.currentPosition.move(s)! case i => s.r1.currentPosition.turn(i)! }! ! if (s.coins.contains(next.point)) {! s.copy(! coins = s.coins - next.point, ! r1 = s.r1.addCoin(next.point).addPosition(next)! )! } else {! s.copy(r1 = s.r1.addPosition(next))! }! }
36. 36. Processing a single instruction def processInstruction(i: Instruction)(s: Playground): Playground = {! val next = i match {! case A => s.r1.currentPosition.move(s)! case i => s.r1.currentPosition.turn(i)! }! We always process the robot on first position ! ! } Robots will be swapped alternatively. if (s.coins.contains(next.point)) {! s.copy(! coins = s.coins - next.point, ! r1 = s.r1.addCoin(next.point).addPosition(next)! )! } else {! s.copy(r1 = s.r1.addPosition(next))! }!
37. 37. Quick reminder trait State[S, +A] {! def run(initial: S): (S, A)! def map[B](f: A => B): State[S, B] = State { s =>! val (s1, a) = run(s)! (s1, f(a))! }! def flatMap[B](f: A => State[S, B]): State[S, B] = State { s =>! val (s1, a) = run(s)! f(a).run(s1)! }! }! object State {! def apply[S, A](f: S => (S, A)): State[S, A] =! new State[S, A] {! def run(initial: S): (S, A) = f(initial)! }! }
38. 38. Introducing new combinators trait State[S, +A] {! ...! }! object State {! def apply[S, A](f: S => (S, A)): State[S, A] =! new State[S, A] {! def run(initial: S): (S, A) = f(initial)! }! ! def get[S]: State[S, S] = State { s => (s, s) }! ! def gets[S, A](f: S => A): State[S, A] = ! State { s => (s, f(s)) }! }
39. 39. Here comes the magic ! def compileInstructions(! i1: List[Instruction], ! i2: List[Instruction]! ): State[Playground, (Score, Score)] = i1 match {! case Nil if i2 == Nil => State.gets(_.scores)! case Nil => State[Playground, (Score, Score)] { s => (s.swapRobots(), s.scores) ! }.flatMap { _ => compileInstructions(i2, i1) }! case head::tail => State[Playground, (Score, Score)] ! { s =>! val s1 = processInstruction(head)(s)! (s1.swapRobots(), s1.scores)! }.flatMap { _ => compileInstructions(i2, tail) }! }
40. 40. Here comes the magic ! def compileInstructions(! i1: List[Instruction], ! i2: List[Instruction]! ): State[Playground, (Score, Score)] = i1 match {! case Nil if i2 == Nil => State.gets(_.scores)! case Nil => State[Playground, (Score, Score)] { s => (s.swapRobots(), s.scores) ! }.flatMap { _ => compileInstructions(i2, i1) }! case head::tail => State[Playground, (Score, Score)] ! { s =>! If both i1 and i2 are empty, we return a State val s1 = processInstruction(head)(s)! Monad with the run method implementation : (s1.swapRobots(), s1.scores)! s => (s, s.scores)! }.flatMap { _ => compileInstructions(i2, tail) }! This will return the Playground passed in argument } and the score as result.
41. 41. Here comes the magic ! def compileInstructions(! i1: List[Instruction], ! i2: List[Instruction]! ): State[Playground, (Score, Score)] = i1 match {! case Nil if i2 == Nil => State.gets(_.scores)! case Nil => State[Playground, (Score, Score)] { s => (s.swapRobots(), s.scores) ! }.flatMap { _ => compileInstructions(i2, Nil) }! case head::tail => State[Playground, (Score, Score)] ! { s =>! val s1 = processInstruction(head)(s)! (s1.swapRobots(), s1.scores)! If i1 is empty, we return a State Monad with a }.flatMap { _ that compileInstructions(i2, tail) }! run method => swap robots in Playground } and returns scores. Then we chain it with the processing of instructions for the second list.
42. 42. Here comes the magic ! We process def compileInstructions(! i1 and return a new Playground where robots are ! i1: List[Instruction],swapped. Then we chain it with the processing of the instructions i2: List[Instruction]! i2 and tail of i1. ): State[Playground, of instructions are processed alternatively {! Lists (Score, Score)] = i1 match ! case Nil if i2 == Nil => State.gets(_.scores)! case Nil => State[Playground, (Score, Score)] { s => (s.swapRobots(), s.scores) ! }.flatMap { _ => compileInstructions(i2, i1) }! case head::tail => State[Playground, (Score, Score)] ! { s =>! val s1 = processInstruction(head)(s)! (s1.swapRobots(), s1.scores)! }.flatMap { _ => compileInstructions(i2, tail) }! }
43. 43. Here comes the magic ! def compileInstructions(! i1: List[Instruction], ! i2: List[Instruction]! ): State[Playground, (Score, Score)] = i1 match {! case Nil if i2 == Nil => State.gets(_.scores)! case Nil => State[Playground, (Score, Score)] { s => (s.swapRobots(), s.scores) ! }.flatMap { _ => compileInstructions(i2, i1) }! case head::tail => State[Playground, (Score, Score)] ! { s =>! val s1 = processInstruction(head)(s)! (s1.swapRobots(), s1.scores)! }.flatMap { _ => compileInstructions(i2, tail) }! }
44. 44. Using for comprehensions def getPositions(p: Playground): (Position, Position) = (p.r1.currentPosition, p.r2.currentPosition)! ! def enhanceResult(! i1: List[Instruction], ! i2: List[Instruction]): State[Playground, (String, (Position, Position))] = {! for {! scores <- compileInstructions(i1, i2)! positions <- State.gets(getPositions)! } yield (declareWinners(scores), positions)! }
45. 45. Conclusion • State Monad simplify computations on states • Use it whenever you want to manipulate states in a purely functional (parsing, caching, validation ...)
46. 46. To learn more about State Monad • Functional programming in scala by Paul Chiusano and Rúnar Bjarnason - This book is awesome ! • State Monad keynote by Michael Pilquist https://speakerdeck.com/mpilquist/scalazstate-monad • Learning scalaz by Eugene Yokota - http:// eed3si9n.com/learning-scalaz/State.html
47. 47. Questions ?