Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Playing with State Monad

5,121 views

Published on

Slides from my conf at scala.io

Published in: Technology
  • Be the first to comment

Playing with State Monad

  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 find 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 define 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 defined 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 definition for Players and Score, • a way to define Robot state • and a way to define 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 ?

×