Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Send + More = Money – ScalaSyd July 2015

Send + More = Money – ScalaSyd July 2015

Let’s mash 2 monads to solve a simple CSP

Intermediate–level Scala talk that cover the 4 blog posts by Bartosz Milewski on solving a simple constraint satisfaction problem in C++ and Haskell.

Starting from an imperative–style implementation we will get to a pure FP–style solution surprisingly similar to the Haskell one promoted by Bartosz.

code: https://goo.gl/0BkWgW
video: https://goo.gl/u71jcH

Filippo Vitale

July 08, 2015
Tweet

More Decks by Filippo Vitale

Other Decks in Programming

Transcript

  1. July 2015 @filippovitale Send + More = Money Let’s merge

    2 monads to solve a simple “constraint satisfaction problem”
  2. Why Monads - Higher order control structures - Monads :

    loops = loops : gotos - Declarative vs. Imperative - Reusable ⊛ Composable ⊛ Factorisable - Easily parallelisable
  3. for { s <- 0 to 9 if s !=

    0 e <- 0 to 9 if e != s n <- 0 to 9 if n != s && n != e d <- 0 to 9 if d != s && d != e && d!=n ... for (int s = 0; s < 10; ++s) for (int e = 0; e < 10; ++e) for (int n = 0; n < 10; ++n) for (int d = 0; d < 10; ++d) ... s != 0 e != s n != s && n != e d != s && d != e && d != n The solution boils down to: ➔ generating all those substitutions ➔ testing the constraints for each one
  4. val listOfDigits = (0 to 9).toList val solutions = for

    { s <- listOfDigits if s != 0 e <- listOfDigits if s != e n <- listOfDigits if s != n && e != n d <- listOfDigits if s != d && e != d && n != d m <- listOfDigits if m != 0 if s != m && e != m && n != m && d != m o <- listOfDigits if s != o && e != o && n != o && d != o && m != o r <- listOfDigits if s != r && e != r && n != r && d != r && m != r && o != r y <- listOfDigits if s != y && e != y && n != y && d != y && m != y && o != y && r != y send = List(s, e, n, d).reduce(_ * 10 + _) more = List(m, o, r, e).reduce(_ * 10 + _) money = List(m, o, n, e, y).reduce(_ * 10 + _) if send + more == money } yield (send, more, money) Search candidate solutions Constraints satisfied?
  5. val listOfDigits = (0 to 9).toList val solutions = for

    { s <- listOfDigits if s != 0 e <- listOfDigits if s != e n <- listOfDigits if s != n && e != n d <- listOfDigits if s != d && e != d && n != d m <- listOfDigits if m != 0 if s != m && e != m && n != m && d != m o <- listOfDigits if s != o && e != o && n != o && d != o && m != o r <- listOfDigits if s != r && e != r && n != r && d != r && m != r && o != r y <- listOfDigits if s != y && e != y && n != y && d != y && m != y && o != y && r != y send = List(s, e, n, d).reduce(_ * 10 + _) more = List(m, o, r, e).reduce(_ * 10 + _) money = List(m, o, n, e, y).reduce(_ * 10 + _) if send + more == money } yield (send, more, money)
  6. 0 1 2 3 4 5 6 7 8 9

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 5 1 2 3 4 0 6 7 8 9 0 6 2 3 4 5 1 7 8 9 0 1 7 3 4 5 6 2 8 9 0 1 3 2 4 5 6 7 8 9 0 1 8 3 4 5 6 7 8 9 0 1 4 3 2 5 6 7 8 9 0 1 9 3 4 5 6 7 8 2
  7. 0 1 2 3 4 5 6 7 8 9

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 5 1 2 3 4 0 6 7 8 9 0 6 2 3 4 5 1 7 8 9 0 1 7 3 4 5 6 2 8 9 0 1 3 2 4 5 6 7 8 9 0 1 8 3 4 5 6 7 8 9 0 1 4 3 2 5 6 7 8 9 0 1 9 3 4 5 6 7 8 2 S = 1
  8. 0 1 2 3 4 5 6 7 8 9

    S = 1 E = …
  9. 0 1 2 3 4 5 6 7 8 9

    S = 1 E = … 0 2 3 4 5 6 7 8 9
  10. trait State[S, +A] { def eval(initial: 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] }
  11. // stateful computation f: S => (S, A) def select(xs:

    List[Int]): (List[Int], Int) = (xs.tail, xs.head)
  12. // stateful computation f: S => (S, A) def select(xs:

    List[Int]): (List[Int], Int) = (xs.tail, xs.head)
  13. // stateful computation f: S => (S, A) def select(xs:

    List[Int]): (List[Int], Int) = (xs.tail, xs.head)
  14. import scalaz.State val s = for { a <- State(select)

    b <- State(select) } yield Map('a' -> a, 'b' -> b) val s = ???
  15. import scalaz.State val s = for { a <- State(select)

    b <- State(select) } yield Map('a' -> a, 'b' -> b) val s: State[ S, A ] = ???
  16. import scalaz.State val s = for { a <- State(select)

    b <- State(select) } yield Map('a' -> a, 'b' -> b) val s: State[ List[Int], Map[Char, Int] ] = ???
  17. import scalaz.State val s = for { a <- State(select)

    b <- State(select) } yield Map('a' -> a, 'b' -> b) val s: State[ List[Int], Map[Char, Int] ] = ??? s.eval(List(1, 2)) ==== Map('a' -> 1, 'b' -> 2)
  18. import scalaz.State val s = for { a <- State(select)

    b <- State(select) } yield Map('a' -> a, 'b' -> b) val s: State[ List[Int], Map[Char, Int] ] = ??? s.eval(List(8, 3)) ==== Map('a' -> 8, 'b' -> 3)
  19. val s = for { a <- State(select) b <-

    State(select) c <- State(select) } yield Map('a' -> a, 'b' -> b, 'c' -> c) s.eval(1 |-> 3) ==== Map('a' -> 1, 'b' -> 2, 'c' -> 3)
  20. val s = for { a <- State(select) b <-

    State(select) c <- State(select) d <- State(select) } yield Map('a' -> a, 'b' -> b, 'c' -> c, 'd' -> d) s.eval(1 |-> 4) ==== Map('a' -> 1, 'b' -> 2, 'c' -> 3, 'd' -> 4)
  21. val l = for { a <- List(1, 2) b

    <- List(1, 2) } yield Map('a' -> a, 'b' -> b) val l = ???
  22. val l = for { a <- List(1, 2) b

    <- List(1, 2) } yield Map('a' -> a, 'b' -> b) List(Map('a' -> 1, 'b' -> 1), Map('a' -> 1, 'b' -> 2), Map('a' -> 2, 'b' -> 1), Map('a' -> 2, 'b' -> 2))
  23. val l = for { a <- List(1, 2) b

    <- List(1, 2) if a != b } yield Map('a' -> a, 'b' -> b) List(Map('a' -> 1, 'b' -> 2), Map('a' -> 2, 'b' -> 1))
  24. def select[A](xs: List[A]): (List[A], A) State S => ( S

    , A) def select[A](xs: List[A]): List[(List[A], A)] StateT[F, S, A] S => F[( S , A)]
  25. 0 1 2 3 4 5 6 7 8 9

    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 5 1 2 3 4 0 6 7 8 9 0 6 2 3 4 5 1 7 8 9 0 1 7 3 4 5 6 2 8 9 0 1 3 2 4 5 6 7 8 9 0 1 8 3 4 5 6 7 8 9 0 1 4 3 2 5 6 7 8 9 0 1 9 3 4 5 6 7 8 2
  26. sl.eval(1 |-> 2) == ??? import scalaz.StateT import scalaz.std.list._ val

    sl = for { a <- StateT(select) b <- StateT(select) } yield Map('a' -> a, 'b' -> b)
  27. import scalaz.StateT import scalaz.std.list._ val sl = for { a

    <- StateT(select) b <- StateT(select) } yield Map('a' -> a, 'b' -> b) sl.eval(1 |-> 2) == List(Map('a' -> 1, 'b' -> 2), Map('a' -> 2, 'b' -> 1))
  28. val sel = StateT(select[Int]) for { s <- sel e

    <- sel n <- sel d <- sel m <- sel o <- sel r <- sel y <- sel … }
  29. val sel = StateT(select[Int]) for { s <- sel if

    s != 0 e <- sel n <- sel d <- sel m <- sel if s != 0 o <- sel r <- sel y <- sel … }
  30. val sel = StateT(select[Int]) for { s <- sel if

    s != 0 e <- sel n <- sel d <- sel m <- sel if s != 0 o <- sel r <- sel y <- sel … } ?
  31. class BooleanOps(self: Boolean) { // ... def guard[M[_]] = new

    GuardPrevent[M] { def apply[A](a: => A) = b.pointOrEmpty[M, A](self)(a) } // ... }
  32. class BooleanOps(self: Boolean) { // ... def guard[M[_]] = new

    GuardPrevent[M] { def apply[A](a: => A) = b.pointOrEmpty[M, A](self)(a) } // ... } (implicit M: Applicative[M], M0: PlusEmpty[M])
  33. class BooleanOps(self: Boolean) { // ... def guard[M[_]] = new

    GuardPrevent[M] { def apply[A](a: => A) = b.pointOrEmpty[M, A](self)(a) } // ... } def pointOrEmpty[M[_], A](cond: Boolean)(a: => A): M[A] = if (cond) M.point(a) else M0.empty
  34. class BooleanOps(self: Boolean) { // ... def guard[M[_]] = new

    GuardPrevent[M] { def apply[A](a: => A) = b.pointOrEmpty[M, A](self)(a) } // ... } def pointOrEmpty[M[_], A](cond: Boolean)(a: => A): M[A] = if (cond) M.point(a) else M0.empty
  35. val g = for { a <- StateT(selectSL[Int]) b <-

    StateT(selectSL[Int]) c <- StateT(selectSL[Int]) } yield Map('a' -> a, 'b' -> b, 'c' -> c) g.eval(1 |-> 3) List( Map(a -> 1, b -> 2, c -> 3), Map(a -> 1, b -> 3, c -> 2), Map(a -> 2, b -> 1, c -> 3), Map(a -> 2, b -> 3, c -> 1), Map(a -> 3, b -> 1, c -> 2), Map(a -> 3, b -> 2, c -> 1) )
  36. type StateL[A] = StateT[List, List[Int], A] val g = for

    { a <- StateT(selectSL[Int]) b <- StateT(selectSL[Int]) c <- StateT(selectSL[Int]) _ <-(a+b==c).guard[StateL](()) } yield Map('a' -> a, 'b' -> b, 'c' -> c) g.eval(1 |-> 3) List( Map(a -> 1, b -> 2, c -> 3), Map(a -> 2, b -> 1, c -> 3), )
  37. type StateL[A] = StateT[List, List[Int], A] val g = for

    { a <- StateT(selectSL[Int]) b <- StateT(selectSL[Int]) c <- StateT(selectSL[Int]) _ <-(a+b==c).guard[StateL](()) } yield Map('a' -> a, 'b' -> b, 'c' -> c) g.eval(1 |-> 3) List( Map(a -> 1, b -> 2, c -> 3), Map(a -> 2, b -> 1, c -> 3), ) MonadPlus MonadPlus
  38. scala> monadPlus.laws[List].check + monad plus.monad.applicative.apply.functor.invariantFunctor.identity: OK, passed 100 tests. +

    monad plus.monad.applicative.apply.functor.invariantFunctor.composite: OK, passed 100 tests. + monad plus.monad.applicative.apply.functor.identity: OK, passed 100 tests. + monad plus.monad.applicative.apply.functor.composite: OK, passed 100 tests. + monad plus.monad.applicative.apply.composition: OK, passed 100 tests. + monad plus.monad.applicative.identity: OK, passed 100 tests. + monad plus.monad.applicative.homomorphism: OK, passed 100 tests. + monad plus.monad.applicative.interchange: OK, passed 100 tests. + monad plus.monad.applicative.map consistent with ap: OK, passed 100 tests. + monad plus.monad.bind.apply.functor.invariantFunctor.identity: OK, passed 100 tests. + monad plus.monad.bind.apply.functor.invariantFunctor.composite: OK, passed 100 tests. + monad plus.monad.bind.apply.functor.identity: OK, passed 100 tests. + monad plus.monad.bind.apply.functor.composite: OK, passed 100 tests. + monad plus.monad.bind.apply.composition: OK, passed 100 tests. + monad plus.monad.bind.associativity: OK, passed 100 tests. + monad plus.monad.bind.ap consistent with bind: OK, passed 100 tests. + monad plus.monad.right identity: OK, passed 100 tests. + monad plus.monad.left identity: OK, passed 100 tests. + monad plus.plusEmpty.plus.semigroup.associative: OK, passed 100 tests. + monad plus.plusEmpty.plus.associative: OK, passed 100 tests. + monad plus.plusEmpty.monoid.semigroup.associative: OK, passed 100 tests. + monad plus.plusEmpty.monoid.left identity: OK, passed 100 tests. + monad plus.plusEmpty.monoid.right identity: OK, passed 100 tests. + monad plus.plusEmpty.left plus identity: OK, passed 100 tests. + monad plus.plusEmpty.right plus identity: OK, passed 100 tests. + monad plus.empty map: OK, passed 100 tests. + monad plus.left zero: OK, passed 100 tests.
  39. type StateL[A] = StateT[List, List[Int], A] val g = for

    { a <- StateT(selectSL[Int]) b <- StateT(selectSL[Int]) c <- StateT(selectSL[Int]) _ <-(a+b==c).guard[StateL](()) _ <- (a>b).prevent[StateL](()) } yield Map('a' -> a, 'b' -> b, 'c' -> c) g.eval(1 |-> 3) List( Map(a -> 1, b -> 2, c -> 3), )
  40. type StateL[A] = StateT[List, List[Int], A] val g = for

    { a <- StateT(selectSL[Int]) b <- StateT(selectSL[Int]) c <- StateT(selectSL[Int]) _ <-(a+b==c).guard[StateL](()) _ <- (a>b).prevent[StateL](()) } yield Map('a' -> a, 'b' -> b, 'c' -> c) g.eval(1 |-> 3) List( Map(a -> 1, b -> 2, c -> 3), ) flatMap
  41. type StateL[A] = StateT[List, List[Int], A] def solutions: StateL[(Int, Int,

    Int)] = ??? solutions.eval(0 |-> 9).head |> println
  42. type StateL[A] = StateT[List, List[Int], A] def select[A](xs: List[A]): List[(List[A],

    A)] = xs map { x => (xs.filterNot(_ == x), x) } def go(chars: List[Char], subst: Map[Char, Int])(i: Int): StateL[(Int, Int, Int)] = ??? def solutions: StateL[(Int, Int, Int)] = StateT(select[Int]) >>= go("sendmoremoney".toList.distinct, Map.empty) solutions.eval(0 |-> 9).head |> println
  43. type StateL[A] = StateT[List, List[Int], A] def select[A](xs: List[A]): List[(List[A],

    A)] = xs map { x => (xs.filterNot(_ == x), x) } def go(chars: List[Char], subst: Map[Char, Int])(i: Int): StateL[(Int, Int, Int)] = chars match { case c :: Nil => prune(subst + (c -> i)) case c :: cs => StateT(select[Int]) >>= go(cs, subst + (c -> i)) } def prune(charToDigit: Map[Char, Int]): StateL[(Int, Int, Int)] = ??? def solutions: StateL[(Int, Int, Int)] = StateT(select[Int]) >>= go("sendmoremoney".toList.distinct, Map.empty) solutions.eval(0 |-> 9).head |> println
  44. type StateL[A] = StateT[List, List[Int], A] def select[A](xs: List[A]): List[(List[A],

    A)] = xs map { x => (xs.filterNot(_ == x), x) } def go(chars: List[Char], subst: Map[Char, Int])(i: Int): StateL[(Int, Int, Int)] = chars match { case c :: Nil => prune(subst + (c -> i)) case c :: cs => StateT(select[Int]) >>= go(cs, subst + (c -> i)) } def prune(charToDigit: Map[Char, Int]): StateL[(Int, Int, Int)] = for { _ <- (charToDigit('m') == 0).prevent[StateL](()) _ <- (charToDigit('s') == 0).prevent[StateL](()) send = "send" map charToDigit reduce (_ * 10 + _) more = "more" map charToDigit reduce (_ * 10 + _) money = "money" map charToDigit reduce (_ * 10 + _) _ <- (send + more == money).guard[StateL](()) } yield (send, more, money) def solutions: StateL[(Int, Int, Int)] = StateT(select[Int]) >>= go("sendmoremoney".toList.distinct, Map.empty) solutions.eval(0 |-> 9).head |> println https://github.com/BartoszMilewski/MoreMoney/blob/master/Scala/src/main/scala/SendMoreMoney.scala
  45. select ::[a] ->[(a, [a])] select [] = [] select(x:xs) =

    (x, xs) : [(y, x:ys) | (y, ys) <-select xs] solve :: StateL [Int] (Int, Int, Int) solve = StateL select >>= go (nub "sendmoremoney") M.empty where go [c] subst i = prune (M.insert c i subst) go (c:cs) subst i = StateL select >>= go cs (M.insert c i subst) prune subst = do guard (get 's' /= 0 && get 'm' /= 0) let send = toNumber "send" more = toNumber "more" money = toNumber "money" guard $ send + more == money return (send, more, money) where get c = fromJust (M.lookup c subst) toNumber str = asNumber (map get str) asNumber = foldl (\t o -> t*10 + o) 0 main = print $ evalStateL solve [0..9] https://github.com/BartoszMilewski/MoreMoney/blob/master/Haskell/Advanced.hs
  46. go [c] subst i = prune (M.insert c i subst)

    go (c:cs) subst i = StateL select >>= go cs (M.insert c i subst) def go(chars: List[Char], subst: Map[Char, Int]) (i: Int) = chars match { case c :: Nil => prune(subst + (c -> i)) case c :: cs => StateT(select[Int]) >>= go(cs, subst + (c -> i)) }
  47. def prune(charToDigit: Map[Char, Int]) = for { _ <- (charToDigit('m')

    == 0).prevent[StateL](()) _ <- (charToDigit('s') == 0).prevent[StateL](()) send = "send" map charToDigit reduce (_ * 10 + _) more = "more" map charToDigit reduce (_ * 10 + _) money = "money" map charToDigit reduce (_ * 10 + _) _ <- (send + more == money).guard[StateL](()) } yield (send, more, money) prune subst = do guard (get 's' /= 0 && get 'm' /= 0) let send = toNumber "send" more = toNumber "more" money = toNumber "money" guard $ send + more == money return (send, more, money) where get c = fromJust (M.lookup c subst) toNumber str = asNumber (map get str) asNumber = foldl (\t o -> t*10 + o) 0
  48. - “The Essence of the Iterator Pattern” - Parallel Execution

    - Different Algorithm - Parallel Genetic Algorithm - Solve it at compile time What’s next? FP-Syd
  49. - “The Essence of the Iterator Pattern” - Parallel Execution

    - Different Algorithm - Parallel Genetic Algorithm - Solve it at compile time FP-Syd scalaz.Traverse What’s next?
  50. - “The Essence of the Iterator Pattern” - Parallel Execution

    - Different Algorithm - Parallel Genetic Algorithm - Solve it at compile time FP-Syd scalaz.ApplicativePlus scalaz.Nondeterminism What’s next?
  51. - “The Essence of the Iterator Pattern” - Parallel Execution

    - Different Algorithm - Parallel Genetic Algorithm - Solve it at compile time FP-Syd (A + B % 10) = (C | 1C) What’s next?
  52. - “The Essence of the Iterator Pattern” - Parallel Execution

    - Different Algorithm - Parallel Genetic Algorithm - Solve it at compile time FP-Syd Cryptarithmetic problems What’s next?
  53. - “The Essence of the Iterator Pattern” - Parallel Execution

    - Different Algorithm - Parallel Genetic Algorithm - Solve it at compile time FP-Syd Peano's arithmetic Shapeless: HList, Sum, … What’s next?
  54. - “The Essence of the Iterator Pattern” - Parallel Execution

    - Different Algorithm - Parallel Genetic Algorithm - Solve it at compile time FP-Syd What’s next?
  55. Resources - 4-part blog at http://BartoszMilewski.com - code: https://github.com/BartoszMilewski/MoreMoney -

    code: https://gist.github.com/filippovitale/6cc45396ed917a2a8411 - Monad Transformers http://goo.gl/VhcJ65 - State Monad https://goo.gl/jt6bvz - EIP http://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf - http://jto.github.io/articles/typelevel_quicksort/ - Parallel Genetic Algorithm http://dl.acm.org/citation.cfm?id=1725467