$30 off During Our Annual Pro Sale. View Details »

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. None
  3. We’re hiring!

  4. @alexeyraga

  5. SEND + MORE ------- MONEY @alexeyraga

  6. SEND + MORE ------- MONEY @alexeyraga

  7. SEND + MORE ------- MONEY @alexeyraga

  8. SEND + MORE ------- MONEY

  9. SEND + MORE ------- MONEY

  10. SEND + MORE ------- MONEY M←1

  11. SEND + MORE ------- MONEY M←1

  12. SEND + MORE ------- MONEY M←1

  13. SEND + 1ORE ------- 1ONEY M←1 O←0 S←8,9

  14. M←1 Hold on a sec… @alexeyraga

  15. M←1 Programmatically! @alexeyraga

  16. None
  17. Immutability Higher-order function Monad Referential transparency

  18. Mon*d

  19. Why Monads - Higher order control structures - Monads :

    loops = loops : gotos - Declarative vs. Imperative - Reusable ⊛ Composable ⊛ Factorisable - Easily parallelisable
  20. Brute force 10!/(10 – 8)! ≈ 2 Million How big

    is the search space?
  21. Brute Force Naïve implementation

  22. 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
  23. 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?
  24. 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)
  25. “Jimmy” and the Multi-verse

  26. None
  27. 0 1 2 3 4 5 6 7 8 9

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

  29. ? ? ? ? ? ? ? ? ? ?

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

    0 S = 0!
  31. None
  32. ? ? ? ? ? ? ? ? ? ?

    S = …
  33. 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
  34. 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
  35. 0 1 2 3 4 5 6 7 8 9

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

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

    S = 1 E = … 0 2 3 4 5 6 7 8 9
  38. “What would Tony Morris do?”

  39. Multi-verse Implementation using the State Monad

  40. “The future is a function of the past.” – A.

    P. Robertson
  41. // stateful computation type State[S, +A] = S => (S,

    A)
  42. 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] }
  43. // stateful computation f: S => (S, A) def select(xs:

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

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

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

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

    b <- State(select) } yield Map('a' -> a, 'b' -> b) val s: State[ S, A ] = ???
  48. 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] ] = ???
  49. 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)
  50. 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)
  51. 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)
  52. 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)
  53. Multi-verse Implementation using List

  54. val l = for { a <- List(1, 2) b

    <- List(1, 2) } yield Map('a' -> a, 'b' -> b) val l = ???
  55. 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))
  56. 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))
  57. None
  58. Multi-verse Implementation using StateT[List, S, A]

  59. None
  60. None
  61. def select[A](xs: List[A]): (List[A], A) State S => ( S

    , A)
  62. 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)]
  63. def select[A](xs: List[A]): List[(List[A], A)] = xs map { x

    => (xs.filterNot(_ == x), x) }
  64. 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
  65. 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)
  66. 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))
  67. val sel = StateT(select[Int]) for { s <- sel e

    <- sel n <- sel d <- sel m <- sel o <- sel r <- sel y <- sel … }
  68. 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 … }
  69. 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 … } ?
  70. Guards via MonadPlus

  71. Functor Applicative Monad MonadPlus

  72. trait PlusEmpty[F[_]] { def empty[A]: F[A] // ... }

  73. class BooleanOps(self: Boolean) { // ... def guard[M[_]] = new

    GuardPrevent[M] { def apply[A](a: => A) = b.pointOrEmpty[M, A](self)(a) } // ... }
  74. 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])
  75. 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
  76. 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
  77. 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) )
  78. 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), )
  79. 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
  80. 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.
  81. 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), )
  82. 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
  83. The Final Implementation

  84. type StateL[A] = StateT[List, List[Int], A] def solutions: StateL[(Int, Int,

    Int)] = ??? solutions.eval(0 |-> 9).head |> println
  85. 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
  86. 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
  87. 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
  88. 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
  89. None
  90. 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)) }
  91. 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
  92. What’s next?

  93. - “The Essence of the Iterator Pattern” - Parallel Execution

    - Different Algorithm - Parallel Genetic Algorithm - Solve it at compile time What’s next? FP-Syd
  94. - “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?
  95. - “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?
  96. - “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?
  97. - “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?
  98. - “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?
  99. - “The Essence of the Iterator Pattern” - Parallel Execution

    - Different Algorithm - Parallel Genetic Algorithm - Solve it at compile time FP-Syd What’s next?
  100. 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
  101. July 2015 @filippovitale Thanks!

  102. July 2015 @filippovitale $ tail -f questions