• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Introducing Monads and State Monad at PSUG
 

Introducing Monads and State Monad at PSUG

on

  • 617 views

 

Statistics

Views

Total Views
617
Views on SlideShare
617
Embed Views
0

Actions

Likes
1
Downloads
11
Comments
0

0 Embeds 0

No embeds

Accessibility

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Introducing Monads and State Monad at PSUG Introducing Monads and State Monad at PSUG Presentation Transcript

    • 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 ?