playing with state monad

47
Playing with the State Monad David Galichet Freelance Developer Twitter : @dgalichet

Upload: david-galichet

Post on 07-May-2015

2.677 views

Category:

Technology


1 download

DESCRIPTION

Slides from my conf at scala.io

TRANSCRIPT

Page 1: Playing with State Monad

Playing with the State MonadDavid Galichet

Freelance Developer

Twitter : @dgalichet

Page 2: Playing with State Monad

Let’s start with a simple problem

0

0 1 2 3

1

2

3

Page 3: Playing with State Monad

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Introducing State Monad

• The aim of the state monad is to abstract over state manipulations

Page 23: Playing with State Monad

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Questions ?