Playing with the State Monad
David Galichet	

Freelance Developer

Twitter : @dgalichet
Wait ! what’s a Monad ?
•

Functor

•

Monad

•

For comprehensions
Wait ! what’s a Monad ?
y !
r e
o d
g i
te s
a in
c
o ry
N o
e
th

•

Functor

•

Monad

•

For comprehensions
Functor
F is a Functor if there is a function :
map(fa: F[A])(f: A => B): F[B]!
that implements the following laws :
1. Identity : map(fa)(Id) == fa!
2. Composition : map(fa)( f ○ g ) ==
map(map(fa)(f))(g)
Functor
F can be seen as a context where a value A rely

F is a Functor if there is a function :
map(fa: F[A])(f: A => B): F[B]!
that implements the following laws :
1. Identity : map(fa)(Id) == fa!
2. Composition : map(fa)( f ○ g ) ==
map(map(fa)(f))(g)
Functor
F is a Functor if there are the following functions :
pure[A](a: A): F[A]!
map(fa: F[A])(f: A => B): F[B]!
Id is the Identity function

that implements the following laws
1. Identity : map(Id) == Id!

2. Composition : map( f ○ g ) == fmap(f) ○
fmap(g)
Functor
trait
def
def
def
}

Functor[F[+_]] {!
pure[A](a: A): F[A]!
map[A,B](fa: F[A])(f: A => B): F[B]!
lift[A,B](f: A => B): F[A] => F[B] = ???!
Functor
trait
def
!
def
!
def
}

Functor[F[+_]] {!
pure[A](a: A): F[A]!

Functor and Monads are also
known as typeclasses

map[A,B](fa: F[A])(f: A => B): F[B]!
lift[A,B](f: A => B): F[A] => F[B] = ???!
Functor
trait
def
!
def
!
def
{ fa:
!

}

Functor[F[_]] {!
pure[A](a: A): F[A]!
map[A,B](fa: F[A])(f: A => B): F[B]!
lift[A,B](f: A => B): F[A] => F[B] = !
F[A] => map(fa)(f) }!
Ex: Maybe is a Functor
sealed trait Maybe[+A]!
case class Value[+A](a: A) extends Maybe[A]!
case object Empty extends Maybe[Nothing]!
!

object Maybe {!
implicit val maybeIsAFunctor = new Functor[Maybe] {!
def pure[A](a: A): Maybe[A] = Value(a)!
def map[A, B](fa: Maybe[A])(f: A => B) = fa match {!
case Empty => Empty!
case Value(a) => Value(f(a))!
}!
}!
}
Ex: Maybe is a Functor
We define a generic function that double the content of a
Functor :
import monads.MaybeIsAFunctor!
def twice[F[+_]](fa: F[Int])(implicit FA: Functor[F]):
F[Int] = FA.map(fa){ x => x*2 }!
!

scala> twice(Value(4): Maybe[Int])!
res1: Value(8)
Monad
M is a Monad if M is an (Applicative) Functor and
there exists the following functions :
unit[A](a: A): M[A]!
bind[A,B](ma: M[A])(f: A => M[B]): M[B]!
Monad
and methods unit and bind implement the following
laws :
1. Left Identity : bind(unit(x))(f) == f(x) !
2. Right Identity : bind(ma)(unit) == ma!
3. Associativity :
bind(bind(ma)(f))(g) == bind(ma){ a =>
bind(f(a))(g) }
Monad
trait Monad[M[+_]] extends Functor[M] {!
def unit[A](a: A): M[A] = pure(a)!
!

def bind[A, B](ma: M[A])(f: A => M[B]): M[B]!
!

def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B] =
bind(ma)(f)!
}!
Ex : Maybe is a Monad
implicit val maybeIsAMonad = new Monad[Maybe] {!
def pure[A](a: A) = Value(a)!
!

def map[A, B](fa: Maybe[A])(f: (A) => B): M[B] = ???!
!

def bind[A, B](ma: Maybe[A])(f: A => Maybe[B]): M[B] =
ma match {!
case Empty => Empty!
case Value(a) => f(a)!
}!
}
Ex : Maybe is a Monad
implicit val maybeIsAMonad = new Monad[Maybe] {!
def pure[A](a: A) = Value(a)!
!

def map[A, B](fa: Maybe[A])(f: (A) => B) = bind(fa)
{ a => unit(f(a)) }!
!

def bind[A, B](ma: Maybe[A])(f: A => Maybe[B]): M[B] =
ma match {!
case Empty => Empty!
case Value(a) => f(a)!
}!
}
Ex : Maybe is a Monad
We define a generic function that add the content of two
Monads :
def add[M[+_]](ma: M[Int], mb: M[Int])(implicit MA:
Monad[M]): M[Int] = !
MA.bind(ma) { x => MA.map(mb) { y => x + y} }!
!

scala> import monads.maybeIsAMonad!
scala> add(Value(4): Maybe[Int], Value(2): Maybe[Int])!
res1: monads.Maybe[Int] = Value(6)
For comprehension
•

Scala provides For Comprehension to simplify
chaining of map and flatMap (equivalent to do
notation in Haskell)

•

At compilation, For Comprehension will be
transformed to a serie of flatMap and map
For comprehension
Scala For Comprehension needs that map and
flatMap to be defined on object directly (not using
typeclass). Here we define a MonadWrapper :
implicit class MonadWrapper[A, M[+_]](ma: M[A])(implicit
MA: Monad[M]) {!
def map[B](f: A => B): M[B] = MA.map(ma)(f)!
!

def flatMap[B](f: A => M[B]): M[B] = MA.flatMap(ma)(f)!
}
For comprehension
import monads.maybeIsAMonad!
!

def add2[M[+_]](ma: M[Int], mb: M[Int])(implicit MA:
Monad[M]): M[Int] = {!
import Monad.MonadWrapper!
for {!
a <- ma!
b <- mb!
} yield a + b!
}!
!

scala> import monads.maybeIsAMonad!
scala> add2(Value(4): Maybe[Int], Value(2): Maybe[Int])!
res2: monads.Maybe[Int] = Value(6)
Generic programming
def sequence[A, M[+_]](ms: List[M[A]])(implicit MA:
Monad[M]): M[List[A]] = ms match {!
case Nil => MA.unit(List.empty[A])!
case head::tail => for {!
x <- head!
xs <- sequence(tail)!
} yield x::xs!
}!
!

import monads.maybeIsAMonad!
!

scala> Monad.sequence(List(Value(1): Maybe[Int],
Value(2): Maybe[Int]))!
res3: monads.Maybe[List[Int]] = Value(List(1, 2))
Let’s continue our journey
with a simple problem
3
2
1
0
0

1

2

3
Rules of the game
•

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
Rules of the game
•

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
Think about this game
•

It appears that we will deal with many states :
•

Playground with its coins

•

Robots with their positions and gathered coins
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
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
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 !
Introducing State Monad

The aim of the state monad is to abstract over state
manipulations
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] = ???!
}
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 !
}
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] = ???!
}
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)!
}!
}
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
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 { ... }
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))!
}
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)
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)!
}
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)!
}
Look 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
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
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))!
}!
}
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))!
}!
}
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)!
}!
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)) }!
}
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) }!
}

!
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(), i1 and i2 are!
s.scores) empty, we return a State
If both
}.flatMap { _ => compileInstructions(i2, i1) }!
Monad with the run method implementation :
case head::tail => State[Playground, (Score, Score)] !
s => (s, s.scores)!
{ s =>!
This will return the Playground passed in argument
val s1 = processInstruction(head)(s)!
and the score as result.
(s1.swapRobots(), s1.scores)!
}.flatMap { _ => compileInstructions(i2, tail) }!
}

!
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 =>! If i1 is empty, we return a State Monad with a run
val s1 method that swap robots in Playground and returns
= processInstruction(head)(s)!
(s1.swapRobots(), s1.scores)!
scores.
}.flatMap { _we chain it with the processing of instructions for
Then => compileInstructions(i2, tail) }!
}
the second list.

!
Here comes the magic !
def compileInstructions(!
i1: List[Instruction], i1 and return a new Playground where
We process !
i2: List[Instruction]!
robots are swapped.
): State[Playground, (Score, Score)] = i1 matchinstructions
Then we chain it with the processing of the {!
case Nil if i2 == Nil of i1.
i2 and tail => State.gets(_.scores)!
case Nil => State[Playground, (Score, Score)] { !s =>
Lists of instructions are processed alternatively
(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) }!
}

!
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) }!
}

!
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)!
}
Conclusion

•

State Monad simplify computations on states

•

Use it whenever you want to manipulate states in a
purely functional (parsing, caching, validation ...)
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
Questions ?

Introducing Monads and State Monad at PSUG

  • 1.
    Playing with theState Monad David Galichet Freelance Developer Twitter : @dgalichet
  • 2.
    Wait ! what’sa Monad ? • Functor • Monad • For comprehensions
  • 3.
    Wait ! what’sa Monad ? y ! r e o d g i te s a in c o ry N o e th • Functor • Monad • For comprehensions
  • 4.
    Functor F is aFunctor if there is a function : map(fa: F[A])(f: A => B): F[B]! that implements the following laws : 1. Identity : map(fa)(Id) == fa! 2. Composition : map(fa)( f ○ g ) == map(map(fa)(f))(g)
  • 5.
    Functor F can beseen as a context where a value A rely F is a Functor if there is a function : map(fa: F[A])(f: A => B): F[B]! that implements the following laws : 1. Identity : map(fa)(Id) == fa! 2. Composition : map(fa)( f ○ g ) == map(map(fa)(f))(g)
  • 6.
    Functor F is aFunctor if there are the following functions : pure[A](a: A): F[A]! map(fa: F[A])(f: A => B): F[B]! Id is the Identity function that implements the following laws 1. Identity : map(Id) == Id! 2. Composition : map( f ○ g ) == fmap(f) ○ fmap(g)
  • 7.
    Functor trait def def def } Functor[F[+_]] {! pure[A](a: A):F[A]! map[A,B](fa: F[A])(f: A => B): F[B]! lift[A,B](f: A => B): F[A] => F[B] = ???!
  • 8.
    Functor trait def ! def ! def } Functor[F[+_]] {! pure[A](a: A):F[A]! Functor and Monads are also known as typeclasses map[A,B](fa: F[A])(f: A => B): F[B]! lift[A,B](f: A => B): F[A] => F[B] = ???!
  • 9.
    Functor trait def ! def ! def { fa: ! } Functor[F[_]] {! pure[A](a:A): F[A]! map[A,B](fa: F[A])(f: A => B): F[B]! lift[A,B](f: A => B): F[A] => F[B] = ! F[A] => map(fa)(f) }!
  • 10.
    Ex: Maybe isa Functor sealed trait Maybe[+A]! case class Value[+A](a: A) extends Maybe[A]! case object Empty extends Maybe[Nothing]! ! object Maybe {! implicit val maybeIsAFunctor = new Functor[Maybe] {! def pure[A](a: A): Maybe[A] = Value(a)! def map[A, B](fa: Maybe[A])(f: A => B) = fa match {! case Empty => Empty! case Value(a) => Value(f(a))! }! }! }
  • 11.
    Ex: Maybe isa Functor We define a generic function that double the content of a Functor : import monads.MaybeIsAFunctor! def twice[F[+_]](fa: F[Int])(implicit FA: Functor[F]): F[Int] = FA.map(fa){ x => x*2 }! ! scala> twice(Value(4): Maybe[Int])! res1: Value(8)
  • 12.
    Monad M is aMonad if M is an (Applicative) Functor and there exists the following functions : unit[A](a: A): M[A]! bind[A,B](ma: M[A])(f: A => M[B]): M[B]!
  • 13.
    Monad and methods unitand bind implement the following laws : 1. Left Identity : bind(unit(x))(f) == f(x) ! 2. Right Identity : bind(ma)(unit) == ma! 3. Associativity : bind(bind(ma)(f))(g) == bind(ma){ a => bind(f(a))(g) }
  • 14.
    Monad trait Monad[M[+_]] extendsFunctor[M] {! def unit[A](a: A): M[A] = pure(a)! ! def bind[A, B](ma: M[A])(f: A => M[B]): M[B]! ! def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B] = bind(ma)(f)! }!
  • 15.
    Ex : Maybeis a Monad implicit val maybeIsAMonad = new Monad[Maybe] {! def pure[A](a: A) = Value(a)! ! def map[A, B](fa: Maybe[A])(f: (A) => B): M[B] = ???! ! def bind[A, B](ma: Maybe[A])(f: A => Maybe[B]): M[B] = ma match {! case Empty => Empty! case Value(a) => f(a)! }! }
  • 16.
    Ex : Maybeis a Monad implicit val maybeIsAMonad = new Monad[Maybe] {! def pure[A](a: A) = Value(a)! ! def map[A, B](fa: Maybe[A])(f: (A) => B) = bind(fa) { a => unit(f(a)) }! ! def bind[A, B](ma: Maybe[A])(f: A => Maybe[B]): M[B] = ma match {! case Empty => Empty! case Value(a) => f(a)! }! }
  • 17.
    Ex : Maybeis a Monad We define a generic function that add the content of two Monads : def add[M[+_]](ma: M[Int], mb: M[Int])(implicit MA: Monad[M]): M[Int] = ! MA.bind(ma) { x => MA.map(mb) { y => x + y} }! ! scala> import monads.maybeIsAMonad! scala> add(Value(4): Maybe[Int], Value(2): Maybe[Int])! res1: monads.Maybe[Int] = Value(6)
  • 18.
    For comprehension • Scala providesFor Comprehension to simplify chaining of map and flatMap (equivalent to do notation in Haskell) • At compilation, For Comprehension will be transformed to a serie of flatMap and map
  • 19.
    For comprehension Scala ForComprehension needs that map and flatMap to be defined on object directly (not using typeclass). Here we define a MonadWrapper : implicit class MonadWrapper[A, M[+_]](ma: M[A])(implicit MA: Monad[M]) {! def map[B](f: A => B): M[B] = MA.map(ma)(f)! ! def flatMap[B](f: A => M[B]): M[B] = MA.flatMap(ma)(f)! }
  • 20.
    For comprehension import monads.maybeIsAMonad! ! defadd2[M[+_]](ma: M[Int], mb: M[Int])(implicit MA: Monad[M]): M[Int] = {! import Monad.MonadWrapper! for {! a <- ma! b <- mb! } yield a + b! }! ! scala> import monads.maybeIsAMonad! scala> add2(Value(4): Maybe[Int], Value(2): Maybe[Int])! res2: monads.Maybe[Int] = Value(6)
  • 21.
    Generic programming def sequence[A,M[+_]](ms: List[M[A]])(implicit MA: Monad[M]): M[List[A]] = ms match {! case Nil => MA.unit(List.empty[A])! case head::tail => for {! x <- head! xs <- sequence(tail)! } yield x::xs! }! ! import monads.maybeIsAMonad! ! scala> Monad.sequence(List(Value(1): Maybe[Int], Value(2): Maybe[Int]))! res3: monads.Maybe[List[Int]] = Value(List(1, 2))
  • 22.
    Let’s continue ourjourney with a simple problem 3 2 1 0 0 1 2 3
  • 23.
    Rules of thegame • 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
  • 24.
    Rules of thegame • 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
  • 25.
    Think about thisgame • It appears that we will deal with many states : • Playground with its coins • Robots with their positions and gathered coins
  • 26.
    We want functionalpurity • 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
  • 27.
    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
  • 28.
    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 !
  • 29.
    Introducing State Monad Theaim of the state monad is to abstract over state manipulations
  • 30.
    Introducing State Monad traitState[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] = ???! }
  • 31.
    Introducing State Monad traitState[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 ! }
  • 32.
    Introducing State Monad traitState[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] = ???! }
  • 33.
    Introducing State Monad traitState[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)! }! }
  • 34.
    Coming back toour 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
  • 35.
    Coming back toour 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 { ... }
  • 36.
    Coming back toour 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))! }
  • 37.
    Coming back toour 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)
  • 38.
    Coming back toour 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)! }
  • 39.
    Coming back toour 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)! }
  • 40.
    Look what wedid • 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
  • 41.
    Let put theseall 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
  • 42.
    Processing a single instruction defprocessInstruction(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))! }! }
  • 43.
    Processing a single instruction defprocessInstruction(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))! }! }
  • 44.
    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)! }!
  • 45.
    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)) }! }
  • 46.
    Here comes themagic ! 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) }! } !
  • 47.
    Here comes themagic ! 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(), i1 and i2 are! s.scores) empty, we return a State If both }.flatMap { _ => compileInstructions(i2, i1) }! Monad with the run method implementation : case head::tail => State[Playground, (Score, Score)] ! s => (s, s.scores)! { s =>! This will return the Playground passed in argument val s1 = processInstruction(head)(s)! and the score as result. (s1.swapRobots(), s1.scores)! }.flatMap { _ => compileInstructions(i2, tail) }! } !
  • 48.
    Here comes themagic ! 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 =>! If i1 is empty, we return a State Monad with a run val s1 method that swap robots in Playground and returns = processInstruction(head)(s)! (s1.swapRobots(), s1.scores)! scores. }.flatMap { _we chain it with the processing of instructions for Then => compileInstructions(i2, tail) }! } the second list. !
  • 49.
    Here comes themagic ! def compileInstructions(! i1: List[Instruction], i1 and return a new Playground where We process ! i2: List[Instruction]! robots are swapped. ): State[Playground, (Score, Score)] = i1 matchinstructions Then we chain it with the processing of the {! case Nil if i2 == Nil of i1. i2 and tail => State.gets(_.scores)! case Nil => State[Playground, (Score, Score)] { !s => Lists of instructions are processed alternatively (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) }! } !
  • 50.
    Here comes themagic ! 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) }! } !
  • 51.
    Using for comprehensions defgetPositions(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)! }
  • 52.
    Conclusion • State Monad simplifycomputations on states • Use it whenever you want to manipulate states in a purely functional (parsing, caching, validation ...)
  • 53.
    To learn moreabout 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
  • 54.