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

Monads, Monoids and Scala

boia01
June 12, 2012

Monads, Monoids and Scala

Introduction to functional programming idioms (e.g. monoids, monads, ...) using Scala

boia01

June 12, 2012
Tweet

More Decks by boia01

Other Decks in Programming

Transcript

  1. Most people intuitively understand this code: for { x <-

    List(1, 2) } yield (x + 2) => List(3, 4)
  2. And most people intuitively understand this code: for { i

    <- 1 until n j <- 1 until (i - 1) if isPrime(i + j) } yield (i, j) => IndexedSeq((4,1), (5,2), (6,1), (7,4), (8,3), (8,5), (9,2), (9,4))
  3. Yet struggle a bit more with this: // desugared from

    previous for-comprehension (1 until n) flatMap { i => (1 until (i – 1)) filter { j => isPrime(i + j) } map { j => (i, j) } } => IndexedSeq((4,1), (5,2), (6,1), (7,4), (8,3), (8,5), (9,2), (9,4))
  4. And even fewer understand why this doesn't compile: for {

    order <- maybeOrder lineItem <- order.lineItems } yield lineItem.product.name [Expected] => Seq(ratchet, hammer) [Actual] => error: type mismatch; found : Seq[Nothing] required: Option[?] lineItem <- order.lineItems ^
  5. Why Talk About Monoids + Monads? Abstract data types representing

    some form of computation Mathematical foundation (category theory) Popular in functional cycles (e.g., Haskell) Learn the jargon so you can understand and not feel (too) intimidated Make your head explode (?!)
  6. No, really – why bother? Think differently about problems (i.e.,

    paradigm shift) Add another tool to your toolset (more power to you!) Refactor + eliminate some boilerplate (through better abstraction) Hopefully, write better programs. (different structuring mechanism for data + flow control)
  7. Caveat Not all programming languages support monadic programming equally. Some

    don't. <cough> Java <cough!> Some do to some extent. <mmmh> Scala <mmmh!> Some pretty much require it. <hmmm> Haskell <hmmm?> Don't try to fit a square peg in a round hole. Don't work against the grain of your language.
  8. Typeclasses Polymorphism without using object orientation Don't have to think

    about types belonging to a big hierarchy of types Instead, think about what the types can act like and then “decorate” them with appropriate typeclasses e.g., an Int can act like a lot of things. It can act like an equatable thing, like an ordered thing, like an enumerable thing, etc. implicit def (x: Int): Equality[Int] = … implicit def (x: Int): Ordered[Int] = … implicit def (x: Int): Enumerable[Int] = … implicit def (x: Int): Numeric[Int] = …
  9. What the %@#! is a Monoid? Algebraic structure 1) identity

    element (Ø) 2) associative binary operation (□) (x: X) □ (Ø: X) ≡ (x: X) (x1: X) □ (x2: X) ≡ (_: X)
  10. Integers under Addition • identity: 0 (zero) • binary operation:

    addition // identity 1 □ 0 => 1 // normal 1 □ 1 => 2
  11. Integers under Addition • identity: 0 (zero) • binary operation:

    addition // identity (1: Plus[Int]) □ (0: Plus[Int]) => (1: Plus[Int]) // normal (1: Plus[Int]) □ (1: Plus[Int]) => (2: Plus[Int])
  12. Integers under Multiplication • identity: 1 • binary operation: multiplication

    // identity (42: Mult[Int]) □ (1: Mult[Int]) => (42: Mult[Int]) // normal (42: Mult[Int]) □ (2: Mult[Int]) => (84: Mult[Int])
  13. Monoid in Scala [2] object StringMonoid extends Monoid[String] { def

    identity: String = "" def combine(x: String, y: String) = x.concat(y) } object IntMonoid extends Monoid[Int] { def identity: Int = 0 def combine(x: Int, y: Int): Int = x + y }
  14. Monoid in Scala [3] A sum method, which works over

    arbitrary monoids, def sum[A](xs: Seq[A])(implicit m: Monoid[A]): A = if (xs.isEmpty) m.identity else m.combine(xs.head, sum(m)(xs.tail) And an be called as follows: sum(List("a", "bc", "def")) => “abcdef” sum(List(1, 2, 3)) => 6
  15. Mo' Monoids! // booleans under logical disjunction (OR) true true

    assert_=== true ⊹ true false assert_=== true ⊹ false true assert_=== true ⊹ false false assert_=== false ⊹ // booleans under logical conjunction (AND) (true | |) (true | |) assert_=== (true | |) ∧ ⊹ ∧ ∧ (true | |) (false | |) assert_=== (false | |) ∧ ⊹ ∧ ∧ (false | |) (true | |) assert_=== (false | |) ∧ ⊹ ∧ ∧ (false | |) (false | |) assert_=== (false | |) ∧ ⊹ ∧ ∧
  16. Mo' Monoids! // Replicate a value using the List Monoid

    and List Pure 42.replicate[List](3) assert_=== List(42, 42, 42)
  17. Mo' Monoids! // Iterate over a function (Int, Int) =>

    (Int, Int) // through the Stream Monoid to generate Fibonacci numbers val fib: Stream[Int] = (0, 1).iterate[Stream] { case (a, b) => (b, a + b) } map (_._1)
  18. Mo' Monoids! // Unfold a value through the List Monoid

    with List Pure 1.unfold[List, String]((a: Int) => (a < 5).option((a.toString, a + 1))) => List("1", "2", "3", "4")
  19. Generic Functions over Monoids sum() a sequence of values repeat()

    a value infinitely replicate() a value n-times unfold() decompose until some condition iterate() generate until some condition ...
  20. Monads as Containers Put stuff in the container Operate on

    values inside the container Flatten containers
  21. Oh, Blueberries! Let's assume you can create boxes to put

    stuff in. object Box { def apply[X](x: X): Box[X] } Now you can put a blueberry in the box, Box.apply(_: Blueberry) => Box[Blueberry]
  22. Err... I'd rather have Strawberries And you can swap blueberries

    for strawberries, val box: Box[Blueberry] val f: Blueberry => Strawberry (box map f) => Box[Strawberry]
  23. [ Boxification ] If you have functions that return Boxes,

    that's fine too. val box: Box[Blueberry] val f: Blueberry => Box[Strawberry] (box flatMap f) => Box[Strawberry]
  24. But Your Can Never Leave! Nothing about the monad interface

    requires the ability to take stuff out of the box! But a few monads do allow you to... e.g. List[_], Option[_], ... One notable monad that insists on keeping everything to itself: IO[_] !
  25. Monads as Computations Run a computation, including “doing nothing” Combine

    two computations X and Y, • Run X, discard result, then run Y • Run X and feed result to Y. * This intuition is generally more useful, but is more difficult to explain, precisely because it is so general.
  26. Mo' Monads! // bind ∗ List(1, 2, 3) List(_, 7)

    => List(1, 7, 2, 7, 3, 7) ∗
  27. Destructuring the Monad Put stuff in the container => “Pure”

    Operate on values inside the container => “Functor” Flatten containers => characteristic of the Monad
  28. Pointed trait Pointed[T[_]] extends Pure[T] // Put stuff in box

    with Functor[T] // Operate on items in box
  29. Recap: Monoids, Monads Design Patterns • Interfaces with nice compositional

    properties • Raise level of abstraction • Lead to code reuse Who knew mathematical abstractions could be useful? :-) But where's the killer app? (Hint: somewhere in your own code)
  30. Challenges [1] Code obfuscation? • “Too many types” / “Complex

    types” • Crazy #$^&@! operators • Some abstractions can be difficult to grok Identification can be surprisingly difficult “Natural” or default abstractions aren't necessarily universal Layering monads becomes geometrically complex • Not a silver bullet against complexity
  31. Challenges [2] Language support (Scala) • Type inference is somewhat

    problematic • Running out of operators • Boilerplate to create/use typeclasses • Performance: boxing overhead • At least we have for-comprehensions ...
  32. “I believe abstraction can help enormously but can also be

    overdone. And everyone has a different limit where more abstraction hurts rather than helps. For instance for me category theory is over the limit but for some others it's fine.” – Martin Odersky, Dec 24th 2009 scala-user@ mailing list
  33. My Verdict I share Martin Odersky's position that many monads

    are generally “over [my] limit” Outside of the usual suspects, need a critical mass of easily identifiable, natural monoids/monads in codebase to warrant using Scalaz Nonetheless, I enjoy seeing Scalaz pushing the functional envelope of Scala I hope the Scala standard library progressively integrate more typeclasses (Equality, Numeric, …) and standardizes functional idioms – in moderation.