0
Upcoming SlideShare
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Standard text messaging rates apply

# Introducing Monads and State Monad at PSUG

750

Published on

5 Likes
Statistics
Notes
• Full Name
Comment goes here.

Are you sure you want to Yes No
Your message goes here
• Be the first to comment

Views
Total Views
750
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
22
0
Likes
5
Embeds 0
No embeds

No notes for slide

### Transcript

• 1. Playing with the State Monad David Galichet Freelance Developer Twitter : @dgalichet
• 2. Wait ! what&#x2019;s a Monad ? &#x2022; Functor &#x2022; Monad &#x2022; For comprehensions
• 3. Wait ! what&#x2019;s a Monad ? y ! r e o d g i te s a in c o ry N o e th &#x2022; Functor &#x2022; Monad &#x2022; For comprehensions
• 4. Functor F is a Functor if there is a function : map(fa: F[A])(f: A =&gt; B): F[B]! that implements the following laws : 1. Identity : map(fa)(Id) == fa! 2. Composition : map(fa)( f &#x25CB; g ) == map(map(fa)(f))(g)
• 5. 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 =&gt; B): F[B]! that implements the following laws : 1. Identity : map(fa)(Id) == fa! 2. Composition : map(fa)( f &#x25CB; g ) == map(map(fa)(f))(g)
• 6. Functor F is a Functor if there are the following functions : pure[A](a: A): F[A]! map(fa: F[A])(f: A =&gt; B): F[B]! Id is the Identity function that implements the following laws 1. Identity : map(Id) == Id! 2. Composition : map( f &#x25CB; g ) == fmap(f) &#x25CB; fmap(g)
• 7. Functor trait def def def } Functor[F[+_]] {! pure[A](a: A): F[A]! map[A,B](fa: F[A])(f: A =&gt; B): F[B]! lift[A,B](f: A =&gt; B): F[A] =&gt; 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 =&gt; B): F[B]! lift[A,B](f: A =&gt; B): F[A] =&gt; 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 =&gt; B): F[B]! lift[A,B](f: A =&gt; B): F[A] =&gt; F[B] = ! F[A] =&gt; map(fa)(f) }!
• 10. 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 =&gt; B) = fa match {! case Empty =&gt; Empty! case Value(a) =&gt; Value(f(a))! }! }! }
• 11. Ex: Maybe is a Functor We de&#xFB01;ne 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 =&gt; x*2 }! ! scala&gt; twice(Value(4): Maybe[Int])! res1: Value(8)
• 12. 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 =&gt; M[B]): M[B]!
• 13. 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 =&gt; bind(f(a))(g) }
• 14. 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 =&gt; M[B]): M[B]! ! def flatMap[A, B](ma: M[A])(f: A =&gt; M[B]): M[B] = bind(ma)(f)! }!
• 15. 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) =&gt; B): M[B] = ???! ! def bind[A, B](ma: Maybe[A])(f: A =&gt; Maybe[B]): M[B] = ma match {! case Empty =&gt; Empty! case Value(a) =&gt; f(a)! }! }
• 16. 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) =&gt; B) = bind(fa) { a =&gt; unit(f(a)) }! ! def bind[A, B](ma: Maybe[A])(f: A =&gt; Maybe[B]): M[B] = ma match {! case Empty =&gt; Empty! case Value(a) =&gt; f(a)! }! }
• 17. Ex : Maybe is a Monad We de&#xFB01;ne 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 =&gt; MA.map(mb) { y =&gt; x + y} }! ! scala&gt; import monads.maybeIsAMonad! scala&gt; add(Value(4): Maybe[Int], Value(2): Maybe[Int])! res1: monads.Maybe[Int] = Value(6)
• 18. For comprehension &#x2022; Scala provides For Comprehension to simplify chaining of map and &#xFB02;atMap (equivalent to do notation in Haskell) &#x2022; At compilation, For Comprehension will be transformed to a serie of &#xFB02;atMap and map
• 19. For comprehension Scala For Comprehension needs that map and &#xFB02;atMap to be de&#xFB01;ned on object directly (not using typeclass). Here we de&#xFB01;ne a MonadWrapper : implicit class MonadWrapper[A, M[+_]](ma: M[A])(implicit MA: Monad[M]) {! def map[B](f: A =&gt; B): M[B] = MA.map(ma)(f)! ! def flatMap[B](f: A =&gt; M[B]): M[B] = MA.flatMap(ma)(f)! }
• 20. 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 &lt;- ma! b &lt;- mb! } yield a + b! }! ! scala&gt; import monads.maybeIsAMonad! scala&gt; 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 =&gt; MA.unit(List.empty[A])! case head::tail =&gt; for {! x &lt;- head! xs &lt;- sequence(tail)! } yield x::xs! }! ! import monads.maybeIsAMonad! ! scala&gt; Monad.sequence(List(Value(1): Maybe[Int], Value(2): Maybe[Int]))! res3: monads.Maybe[List[Int]] = Value(List(1, 2))
• 22. Let&#x2019;s continue our journey with a simple problem 3 2 1 0 0 1 2 3
• 23. Rules of the game &#x2022; We want to simulate two robots moving through a nxm playground &#x2022; Each robot can either turn on a direction (North, South, East, West) or move one step forward &#x2022; Robots move or turn according to instructions
• 24. Rules of the game &#x2022; A robot can&#x2019;t go out of the playground &#x2022; A robot will be blocked if another robot is on the place &#x2022; Some coins are spread on the playground &#x2022; Robots gather coins when they move over it
• 25. Think about this game &#x2022; It appears that we will deal with many states : &#x2022; Playground with its coins &#x2022; Robots with their positions and gathered coins
• 26. We want functional purity &#x2022; Functional Purity has many advantages like composability, idempotence, maintainability and thread safety &#x2022; We need to &#xFB01;nd a way to deal with states and remain pure
• 27. Dealing with states S =&gt; (S, A) &#x2022; S is the type of a state and A the type of a computation &#x2022; The outcome of this function is a new state and a result
• 28. Chaining states computations def chainStOps(! c1: S =&gt; (S, A), ! c2: S =&gt; (S, A)! ): S =&gt; (S, A) = { s =&gt;! val (s1, _) = c1(s)! c2(s1)! } Repeated many times, this can be error prone !
• 29. Introducing State Monad The aim of the state monad is to abstract over state manipulations
• 30. Introducing State Monad trait State[S, +A] {! def run(initial: S): (S, A)! def map[B](f: A =&gt; B): State[S, B] = ???! def flatMap[B](f: A =&gt; State[S, B]): State[S, B] = ???! }! ! object State {! def apply[S, A](f: S =&gt; (S, A)): State[S, A] = ???! }
• 31. Introducing State Monad trait State[S, +A] {! def run(initial: S): (S, A)! def map[B](f: A =&gt; B): State[S, B] = ???! def flatMap[B](f: A =&gt; State[S, B]): State[S, B] = ???! }! ! object State {! def apply[S, A](f: S =&gt; (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 trait State[S, +A] {! def run(initial: S): (S, A)! ! def map[B](f: A =&gt; B): State[S, B] = State { s =&gt;! val (s1, a) = run(s)! (s1, f(a))! Don&#x2019;t forget the definition: }! State.apply(S =&gt; (S, A)): State[S,A] ! ! ! def flatMap[B](f: A =&gt; State[S, B]): State[S, B] = ???! }
• 33. Introducing State Monad trait State[S, +A] {! def run(initial: S): (S, A)! ! def map[B](f: A =&gt; B): State[S, B] = State { s =&gt;! val (s1, a) = run(s)! (s1, f(a))! }! Don&#x2019;t forget the definition: ! State.apply(S =&gt; (S, A)): State[S,A] ! ! def flatMap[B](f: A =&gt; State[S, B]): State[S, B] = ! State { s =&gt;! val (s1, a) = run(s)! f(a).run(s1)! }! }
• 34. Coming back to our game ! &#x2022; 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 to our game ! &#x2022; 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 =&gt; West! case R =&gt; East! case _ =&gt; this! }! }! case object South extends Direction { ... }! case object East extends Direction { ... }! case object West extends Direction { ... }
• 36. Coming back to our game ! &#x2022; A direction and a location de&#xFB01;ne 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 =&gt; copy(point = point.copy(y = point.y + 1))! case South =&gt; ...! }! if (s.isPossiblePosition(p1)) p1 else this! }! def turn(instruction: Instruction): Position = ! copy(direction = direction.turn(instruction))! }
• 37. Coming back to our game ! &#x2022; 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 to our game ! &#x2022; The state of each Robot is de&#xFB01;ned 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 to our game ! &#x2022; 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 &lt;= point.x &amp;&amp; ...! ! def isPossiblePosition(pos: Position): Boolean = ...! ! lazy val scores = (r1.score, r2.score)! ! def swapRobots(): Playground = copy(r1 = r2, r2 = r1)! }
• 40. Look what we did &#x2022; a set of Instructions, &#x2022; a Position composed with Points and Direction, &#x2022; a de&#xFB01;nition for Players and Score, &#x2022; a way to de&#xFB01;ne Robot state &#x2022; and a way to de&#xFB01;ne Playground state
• 41. Let put these all together ! &#x2022; Now, we need a method to process a single instruction &#x2022; And a method to process all instructions &#x2022; The expected result is a State Monad that will be run with the initial state of the playground
• 42. Processing a single instruction def processInstruction(i: Instruction)(s: Playground): Playground = {! val next = i match {! case A =&gt; s.r1.currentPosition.move(s)! case i =&gt; 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 def processInstruction(i: Instruction)(s: Playground): Playground = {! val next = i match {! case A =&gt; s.r1.currentPosition.move(s)! case i =&gt; 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 =&gt; B): State[S, B] = State { s =&gt;! val (s1, a) = run(s)! (s1, f(a))! }! def flatMap[B](f: A =&gt; State[S, B]): State[S, B] = ! State { s =&gt;! val (s1, a) = run(s)! f(a).run(s1)! }! }! object State {! def apply[S, A](f: S =&gt; (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 =&gt; (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 =&gt; (s, s) }! ! def gets[S, A](f: S =&gt; A): State[S, A] = ! State { s =&gt; (s, f(s)) }! }
• 46. Here comes the magic ! def compileInstructions(! i1: List[Instruction], ! i2: List[Instruction]! ): State[Playground, (Score, Score)] = i1 match {! case Nil if i2 == Nil =&gt; State.gets(_.scores)! case Nil =&gt; State[Playground, (Score, Score)] { s =&gt; (s.swapRobots(), s.scores) ! }.flatMap { _ =&gt; compileInstructions(i2, i1) }! case head::tail =&gt; State[Playground, (Score, Score)] ! { s =&gt;! val s1 = processInstruction(head)(s)! (s1.swapRobots(), s1.scores)! }.flatMap { _ =&gt; compileInstructions(i2, tail) }! } !
• 47. Here comes the magic ! def compileInstructions(! i1: List[Instruction], ! i2: List[Instruction]! ): State[Playground, (Score, Score)] = i1 match {! case Nil if i2 == Nil =&gt; State.gets(_.scores)! case Nil =&gt; State[Playground, (Score, Score)] { s =&gt; (s.swapRobots(), i1 and i2 are! s.scores) empty, we return a State If both }.flatMap { _ =&gt; compileInstructions(i2, i1) }! Monad with the run method implementation : case head::tail =&gt; State[Playground, (Score, Score)] ! s =&gt; (s, s.scores)! { s =&gt;! This will return the Playground passed in argument val s1 = processInstruction(head)(s)! and the score as result. (s1.swapRobots(), s1.scores)! }.flatMap { _ =&gt; compileInstructions(i2, tail) }! } !
• 48. Here comes the magic ! def compileInstructions(! i1: List[Instruction], ! i2: List[Instruction]! ): State[Playground, (Score, Score)] = i1 match {! case Nil if i2 == Nil =&gt; State.gets(_.scores)! case Nil =&gt; State[Playground, (Score, Score)] { s =&gt; (s.swapRobots(), s.scores) ! }.flatMap { _ =&gt; compileInstructions(i2, Nil) }! case head::tail =&gt; State[Playground, (Score, Score)] ! { s =&gt;! 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 =&gt; compileInstructions(i2, tail) }! } the second list. !
• 49. 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 =&gt; State.gets(_.scores)! case Nil =&gt; State[Playground, (Score, Score)] { !s =&gt; Lists of instructions are processed alternatively (s.swapRobots(), s.scores) ! }.flatMap { _ =&gt; compileInstructions(i2, i1) }! case head::tail =&gt; State[Playground, (Score, Score)] ! { s =&gt;! val s1 = processInstruction(head)(s)! (s1.swapRobots(), s1.scores)! }.flatMap { _ =&gt; compileInstructions(i2, tail) }! } !
• 50. Here comes the magic ! def compileInstructions(! i1: List[Instruction], ! i2: List[Instruction]! ): State[Playground, (Score, Score)] = i1 match {! case Nil if i2 == Nil =&gt; State.gets(_.scores)! case Nil =&gt; State[Playground, (Score, Score)] { s =&gt; (s.swapRobots(), s.scores) ! }.flatMap { _ =&gt; compileInstructions(i2, i1) }! case head::tail =&gt; State[Playground, (Score, Score)] ! { s =&gt;! val s1 = processInstruction(head)(s)! (s1.swapRobots(), s1.scores)! }.flatMap { _ =&gt; compileInstructions(i2, tail) }! } !
• 51. 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 &lt;- compileInstructions(i1, i2)! positions &lt;- State.gets(getPositions)! } yield (declareWinners(scores), positions)! }
• 52. Conclusion &#x2022; State Monad simplify computations on states &#x2022; Use it whenever you want to manipulate states in a purely functional (parsing, caching, validation ...)
• 53. To learn more about State Monad &#x2022; Functional programming in Scala by Paul Chiusano and R&#xFA;nar Bjarnason - This book is awesome ! &#x2022; State Monad keynote by Michael Pilquist - https:// speakerdeck.com/mpilquist/scalaz-state-monad &#x2022; Learning scalaz by Eugene Yokota - http:// eed3si9n.com/learning-scalaz/State.html
• 54. Questions ?