Slide 1

Slide 1 text

Monads & Monoids Monads & Monoids

Slide 2

Slide 2 text

Dark or Dark or White White Magic? Magic?

Slide 3

Slide 3 text

The “Funny” Thing about Monads

Slide 4

Slide 4 text

Most people intuitively understand this code: for { x <- List(1, 2) } yield (x + 2) => List(3, 4)

Slide 5

Slide 5 text

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))

Slide 6

Slide 6 text

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))

Slide 7

Slide 7 text

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 ^

Slide 8

Slide 8 text

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 (?!)

Slide 9

Slide 9 text

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)

Slide 10

Slide 10 text

Caveat Not all programming languages support monadic programming equally. Some don't. Java Some do to some extent. Scala Some pretty much require it. Haskell Don't try to fit a square peg in a round hole. Don't work against the grain of your language.

Slide 11

Slide 11 text

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] = …

Slide 12

Slide 12 text

What the %@#! is a Monoid? Algebraic structure 1) identity element (Ø) 2) associative binary operation (□) (x: X) □ (Ø: X) ≡ (x: X) (x1: X) □ (x2: X) ≡ (_: X)

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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])

Slide 15

Slide 15 text

Integers under Multiplication ● identity: 1 ● binary operation: multiplication // identity 42 □ 1 => 42 // normal 42 □ 2 => 84

Slide 16

Slide 16 text

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])

Slide 17

Slide 17 text

Other Monoids Strings List[X] Hour on clock Functions SQL aggregates, e.g., sum(visitors) ...

Slide 18

Slide 18 text

Monoid in Scala trait Monoid[T] { def identity: T def combine(x: T, y: T): T }

Slide 19

Slide 19 text

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 }

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Monoid in Scala [4] Alternatively, def sum[A](xs: Seq[A])(implicit m: Monoid[A]): A = xs.foldLeft(m.identity) { _ + _ }

Slide 22

Slide 22 text

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 | |) ∧ ⊹ ∧ ∧

Slide 23

Slide 23 text

Mo' Monoids! // Replicate a value using the List Monoid and List Pure 42.replicate[List](3) assert_=== List(42, 42, 42)

Slide 24

Slide 24 text

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)

Slide 25

Slide 25 text

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")

Slide 26

Slide 26 text

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 ...

Slide 27

Slide 27 text

The Monad! (demonas)

Slide 28

Slide 28 text

Monads as Containers Put stuff in the container Operate on values inside the container Flatten containers

Slide 29

Slide 29 text

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]

Slide 30

Slide 30 text

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]

Slide 31

Slide 31 text

[ 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]

Slide 32

Slide 32 text

Decluttering! You can also flatten your boxes, nestedBoxes: Box[Box[Blueberry] nestedBoxes.flatten => Box[Blueberry]

Slide 33

Slide 33 text

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[_] !

Slide 34

Slide 34 text

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.

Slide 35

Slide 35 text

Mo' Monads! // bind ∗ List(1, 2, 3) List(_, 7) => List(1, 7, 2, 7, 3, 7) ∗

Slide 36

Slide 36 text

Examples List[X] Option[X]

Slide 37

Slide 37 text

Destructuring the Monad Put stuff in the container => “Pure” Operate on values inside the container => “Functor” Flatten containers => characteristic of the Monad

Slide 38

Slide 38 text

Pure // Put stuff in the container trait Pure[T[_]] { def pure[A](a: A): T[A] }

Slide 39

Slide 39 text

Functor // Operate on values inside container trait Functor[T[X]] { def map[Y](f: X => Y): T[Y] }

Slide 40

Slide 40 text

Pointed trait Pointed[T[_]] extends Pure[T] // Put stuff in box with Functor[T] // Operate on items in box

Slide 41

Slide 41 text

Applicative trait Applicative[T[A]] extends Pointed[T] { def apply[B](f: T[A => B]): T[B] }

Slide 42

Slide 42 text

Monad in Scala trait Monad[T[A]] extends Pure[T], Functor[T] Pointed[T], Applicative[T] { def flatMap[B](f: A => T[B]): T[B] }

Slide 43

Slide 43 text

Typeclassopedia

Slide 44

Slide 44 text

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)

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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 ...

Slide 47

Slide 47 text

“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

Slide 48

Slide 48 text

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.