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

Introducing Monads and State Monad at PSUG

Embed Size (px)

Citation preview

Page 1: Introducing Monads and State Monad at PSUG

Playing with the State Monad

David Galichet Freelance Developer

Twitter : @dgalichet

Page 2: Introducing Monads and State Monad at PSUG

Wait ! what’s a Monad ?

• Functor

• Monad

• For comprehensions

Page 3: Introducing Monads and State Monad at PSUG

Wait ! what’s a Monad ?

• Functor

• Monad

• For comprehensionsN

o category

theory inside !

Page 4: Introducing Monads and State Monad at PSUG

FunctorF 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)

Page 5: Introducing Monads and State Monad at PSUG

FunctorF 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)

F can be seen as a context where a value A rely

Page 6: Introducing Monads and State Monad at PSUG

FunctorF is a Functor if there are the following functions :

pure[A](a: A): F[A]!

map(fa: F[A])(f: A => B): F[B]!

that implements the following laws

1. Identity : map(Id) == Id!

2. Composition : map( f ○ g ) == fmap(f) ○ fmap(g)

Id is the Identity function

Page 7: Introducing Monads and State Monad at PSUG

Functor

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

}

Page 8: Introducing Monads and State Monad at PSUG

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

}

Functor and Monads are also known as typeclasses

Page 9: Introducing Monads and State Monad at PSUG

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

!}

Page 10: Introducing Monads and State Monad at PSUG

Ex: Maybe is a Functorsealed 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))! }! }!}

Page 11: Introducing Monads and State Monad at PSUG

Ex: Maybe is 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)

We define a generic function that double the content of a Functor :

Page 12: Introducing Monads and State Monad at PSUG

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]!

Page 13: Introducing Monads and State Monad at PSUG

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

Page 14: Introducing Monads and State Monad at PSUG

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

Page 15: Introducing Monads and State Monad at PSUG

Ex : Maybe is a Monadimplicit 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)! }!}

Page 16: Introducing Monads and State Monad at PSUG

Ex : Maybe is a Monadimplicit 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)! }!}

Page 17: Introducing Monads and State Monad at PSUG

Ex : Maybe is a Monad

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)

We define a generic function that add the content of two Monads :

Page 18: Introducing Monads and State Monad at PSUG

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

Page 19: Introducing Monads and State Monad at PSUG

For comprehension

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

Scala For Comprehension needs that map and flatMap to be defined on object directly (not using typeclass). Here we define a MonadWrapper :

Page 20: Introducing Monads and State Monad at PSUG

For comprehensionimport 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)

Page 21: Introducing Monads and State Monad at PSUG

Generic programmingdef 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))

Page 22: Introducing Monads and State Monad at PSUG

0

0 1 2 3

1

2

3

Let’s continue our journey with a simple problem

Page 23: Introducing Monads and State Monad at PSUG

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

Page 24: Introducing Monads and State Monad at PSUG

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

Page 25: Introducing Monads and State Monad at PSUG

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 26: Introducing Monads and State Monad at PSUG

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 27: Introducing Monads and State Monad at PSUG

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 28: Introducing Monads and State Monad at PSUG

Chaining states computations

Repeated many times, this can be error prone !

def chainStOps(! c1: S => (S, A), ! c2: S => (S, A)!): S => (S, A) = { s =>! val (s1, _) = c1(s)! c2(s1)!}

Page 29: Introducing Monads and State Monad at PSUG

Introducing State Monad

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

Page 30: Introducing Monads and State Monad at PSUG

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] = ???!}

Page 31: Introducing Monads and State Monad at PSUG

Introducing State Monad

State Monad embed computation !

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

Page 32: Introducing Monads and State Monad at PSUG

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 33: Introducing Monads and State Monad at PSUG

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 34: Introducing Monads and State Monad at PSUG

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 35: Introducing Monads and State Monad at PSUG

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 { ... }

Page 36: Introducing Monads and State Monad at PSUG

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

Page 37: Introducing Monads and State Monad at PSUG

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)

Page 38: Introducing Monads and State Monad at PSUG

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 39: Introducing Monads and State Monad at PSUG

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 40: Introducing Monads and State Monad at PSUG

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

Page 41: Introducing Monads and State Monad at PSUG

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 42: Introducing Monads and State Monad at PSUG

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

Page 43: Introducing Monads and State Monad at PSUG

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

We always process the robot on first position ! Robots will be swapped alternatively.

Page 44: Introducing Monads and State Monad at PSUG

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 45: Introducing Monads and State Monad at PSUG

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

Page 46: Introducing Monads and State Monad at PSUG

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 47: Introducing Monads and State Monad at PSUG

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 48: Introducing Monads and State Monad at PSUG

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 49: Introducing Monads and State Monad at PSUG

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 50: Introducing Monads and State Monad at PSUG

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 51: Introducing Monads and State Monad at PSUG

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 52: Introducing Monads and State Monad at PSUG

Conclusion

• State Monad simplify computations on states

• Use it whenever you want to manipulate states in a purely functional (parsing, caching, validation ...)

Page 53: Introducing Monads and State Monad at PSUG

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 54: Introducing Monads and State Monad at PSUG

Questions ?