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. 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. 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. 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. 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. 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 !
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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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) }!
}
45. Conclusion
• State Monad simplify computations on states
• Use it whenever you want to manipulate
states in a purely functional (parsing,
caching, validation ...)
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