playing with state monad
DESCRIPTION
Slides from my conf at scala.ioTRANSCRIPT
![Page 1: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/1.jpg)
Playing with the State MonadDavid Galichet
Freelance Developer
Twitter : @dgalichet
![Page 2: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/2.jpg)
Let’s start with a simple problem
0
0 1 2 3
1
2
3
![Page 3: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/3.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 0 Score : 0
![Page 4: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/4.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 1 Score : 0
![Page 5: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/5.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 1 Score : 0
![Page 6: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/6.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 1 Score : 0
![Page 7: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/7.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 1 Score : 0
![Page 8: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/8.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 1 Score : 0
![Page 9: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/9.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 1 Score : 1
![Page 10: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/10.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 2 Score : 1
![Page 11: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/11.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 2 Score : 1
![Page 12: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/12.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 2 Score : 1
R1 is blocked by R2 !
![Page 13: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/13.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 2 Score : 1
![Page 14: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/14.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 2 Score : 1
![Page 15: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/15.jpg)
A simple simulation
0
0 1 2 3
1
2
3R1 R2(A, A, R, A, A, A) (A, R, A, L, A, A)
Score : 2 Score : 1
![Page 16: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/16.jpg)
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
![Page 17: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/17.jpg)
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
![Page 18: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/18.jpg)
Think about this game
• It appears that we will deal with many states :
• Playground with its coins
• Robots with their positions and gathered coins
![Page 19: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/19.jpg)
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
![Page 20: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/20.jpg)
Dealing with states
• 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
S => (S, A)
![Page 21: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/21.jpg)
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 !
![Page 22: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/22.jpg)
Introducing State Monad
• The aim of the state monad is to abstract over state manipulations
![Page 23: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/23.jpg)
Introducing State Monadtrait 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] = ???!}
![Page 24: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/24.jpg)
Introducing State Monadtrait 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 !
![Page 25: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/25.jpg)
Introducing State Monadtrait 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] = ???!}
Don’t forget the definition: State.apply(S => (S, A)): State[S,A]
![Page 26: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/26.jpg)
Introducing State Monadtrait 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)! }!}
Don’t forget the definition: State.apply(S => (S, A)): State[S,A]
![Page 27: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/27.jpg)
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
![Page 28: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/28.jpg)
Coming back to our game !• Each robot has a directionsealed 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 { ... }
![Page 29: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/29.jpg)
Coming back to our game !• A direction and a location define a positioncase 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))!}
![Page 30: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/30.jpg)
Coming back to our game !• And each Robot is a player with a Scoresealed trait Player!case object R1 extends Player!case object R2 extends Player!!case class Score(player: Player, score: Int)
![Page 31: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/31.jpg)
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)!}
![Page 32: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/32.jpg)
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)!}
![Page 33: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/33.jpg)
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
![Page 34: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/34.jpg)
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
![Page 35: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/35.jpg)
Processing a single instructiondef 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))! }!}
![Page 36: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/36.jpg)
Processing a single instructiondef 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))! }!}
We always process the robot on first position ! Robots will be swapped alternatively.
![Page 37: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/37.jpg)
Quick remindertrait 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)! }!}
![Page 38: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/38.jpg)
Introducing new combinatorstrait 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)) }!}
![Page 39: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/39.jpg)
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) }!}
![Page 40: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/40.jpg)
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) }!}
If both i1 and i2 are empty, we return a State Monad with the run method implementation :
s => (s, s.scores)!This will return the Playground passed in argument and the score as result.
![Page 41: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/41.jpg)
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)! }.flatMap { _ => compileInstructions(i2, tail) }!}
If i1 is empty, we return a State Monad with a run method that swap robots in Playground and returns scores. Then we chain it with the processing of instructions for the second list.
![Page 42: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/42.jpg)
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) }!}
We process i1 and return a new Playground where robots are swapped. Then we chain it with the processing of the instructions i2 and tail of i1. Lists of instructions are processed alternatively !
![Page 43: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/43.jpg)
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) }!}
![Page 44: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/44.jpg)
Using for comprehensionsdef 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)!}
![Page 45: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/45.jpg)
Conclusion
• State Monad simplify computations on states
• Use it whenever you want to manipulate states in a purely functional (parsing, caching, validation ...)
![Page 46: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/46.jpg)
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/scalaz-state-monad
• Learning scalaz by Eugene Yokota - http://eed3si9n.com/learning-scalaz/State.html
![Page 47: Playing with State Monad](https://reader033.vdocument.in/reader033/viewer/2022052504/554a585cb4c905572f8b4dac/html5/thumbnails/47.jpg)
Questions ?