This document summarizes a presentation about monads in Scala. It discusses how monads allow structuring computations and combining them. Some key monads described include Option for handling failures, State for managing state, and Identity. For comprehensions in Scala emulate do notation in Haskell. Monads are demonstrated through an evaluator for arithmetic expressions that uses different monadic types like Identity, Option and State.
Martin Fowler's Refactoring Techniques Quick Reference
Report
Share
1 of 23
More Related Content
Grokking Monads in Scala
1. Grokking Monads in Scala St. Louis Lambda Lounge August 5, 2010 Tim Dalton Senior Software Engineer Object Computing Inc.
2. Monads Are… Just a monoid in the category of endofunctors. Like “duh”!
3. Monads Are… A way to structure computations A strategy for combining computations into more complex computations (credit: Julien Wetterwald)
4. Monads Are… In Haskell, two core functions (>>=) :: m a -> (a -> m b) -> m b return :: a -> m a Monad Laws Left Unit: (return a) >>= k = k a Right Unit m >>= (return) = m Associative m >>= ( -> (k a) >>= ( -> h b)) = (m >>= ( -> k a)) >>= ( -> h b)
5. Haskell Monads Haskell supports “do notation” for chaining monadic computations: do { x <- Just (3+5) y <- Just (5*7) return (x-y) } Which is “sugar” for: Just (3+5) >>= -> Just (5*7) >>= -> return (x-y) Should evaluate to: Just(-27)
6. Scala "For comprehensions" for (i <- 1 to 5) yield i scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5) for (i <- 1 to 5 if i % 2 == 0) yield i scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4) for (i <-1 to 5 if i % 2 == 0) { print (i + " " ) } 2 4 for (i <-1 to 5 if i % 2 == 0; j <- 1 to 5 if j % 2 != 0) yield ( i * j ) scala.collection.immutable.IndexedSeq[Int] = Vector(2, 6, 10, 4, 12, 20) for (i <-1 to 5 if i % 2 == 0; j <- 1 to 5 if j % 2 != 0; k <- 1 to 5) yield ( i * j / k ) scala.collection.immutable.IndexedSeq[Int] = Vector(2, 1, 0, 0, 0, 6, 3, 2, 1, 1, 10, 5, 3, 2, 2, 4, 2, 1, 1, 0, 12, 6, 4, 3, 2, 20, 10, 6, 5, 4)
7. De-sugarized For comprehensions (1 to 5).map(identity) scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5) (1 to 5).filter{_ % 2 == 0}.map(identity) scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4) (1 to 5).filter{_ % 2 == 0}.foreach { i => print (i + " " ) } 2 4 (1 to 5).filter{_ % 2 == 0}.flatMap { i => (1 to 5).filter{_ % 2 != 0}.map{ j => i * j } } scala.collection.immutable.IndexedSeq[Int] = Vector(2, 6, 10, 4, 12, 20) (1 to 5).filter{_ % 2 == 0}.flatMap { i => (1 to 5).filter{_ % 2 != 0}.flatMap{ j => (1 to 5).map{ k => i * j / k } } } scala.collection.immutable.IndexedSeq[Int] = Vector(2, 1, 0, 0, 0, 6, 3, 2, 1, 1, 10, 5, 3, 2, 2, 4, 2, 1, 1, 0, 12, 6, 4, 3, 2, 20, 10, 6, 5, 4)
8. A Monadic Trait abstract trait M[A] { def unit[B] (value : B):M[B] def map[B](f: A => B) : M[B] = flatMap {x => unit(f(x))} def flatMap[B](f: A => M[B]) : M[B] } Scala flatMap correlates to Haskell’s bind (>>=) Scala map can be expressed in terms of flatMap or vice versa Some implementations use map and flatten Haskell convention for flatten is “join” Trait is used for illustration. There are many ways to implement monads in Scala.
9. Simplest Monad – Identity case class Identity[A](value:A) { def map[B](f:(A) => B) = Identity(f(value)) def flatMap[B](f:(A) => Identity[B]) = f(value) }
10. AST Evaluator An evaluator for an Abstract Syntax Tree (AST) is going to implemented for illustration Scala Trait for evaluator: trait EvaluatorTrait[A,M] { def eval(a:A):M; } M will be a Monadic type Example used derives from Phillip Wadler’s “Monads for functional programming” paper. Can only handle integer constants and division operations sealed abstract class Term() case class Constant(value:Int) extends Term case class Divide(a:Term, b:Term) extends Term
11. AST Evaluator – Identity object IdentityEvaluator extends EvaluatorTrait[Term, Identity[Int]] { def eval(term: Term) = term match { case Constant(x) => Identity(x) case Divide(a,b) => for (bp <- eval(b); ap <- eval(a)) yield (ap/bp) } println(eval(Divide(Divide(Constant(1972),Constant(2)), Constant(23)))) Identity(42) println(eval(Divide(Constant(1),Constant(0)))) Exception in thread "main" java.lang.ArithmeticException: / by zero
12. Useful Monad - Option sealed abstract class Option[+A] extends Product { def map[B](f: A => B): Option[B] = if (isEmpty) None else Some(f(this.get)) def flatMap[B](f: A => Option[B]): Option[B] = if (isEmpty) None else f(this.get) } final case class Some[+A](x: A) extends Option[A] { def isEmpty = false def get = x } case object None extends Option[Nothing] { def isEmpty = true def get = throw new NoSuchElementException("None.get") } Also referred to as the Maybe or Failure monad. Haskell supports Just/Nothing that correlates to Some/None in Scala
17. “ Wonkier” Monad – State object State { def unit[S,A](a:A) = new State((s:S) => (s, a)) } case class State[S, A](val s:S => (S, A)) { def map[B](f: A => B): State[S,B] = flatMap((a:A) => State.unit(f(a))) def flatMap[B](f: A => State[S,B]): State[S,B] = State((x:S) => { val (a,y) = s(x) f(y).s(a) }) }
18. State Monad val add = (x:Int, y:Int) => State[List[String], Int]((s:List[String]) => { ((x + " + " + y + " = " + (x + y)) :: s, (x + y)) }) val sub = (x:Int, y:Int) => State[List[String], Int]((s:List[String]) => { ((x + " - " + y + " = " + (x - y)) :: s, (x - y)) }) val f = for (x1 <- add(2 , 2); x2 <- sub(x1, 5); x3 <- add(x2, 2)) yield (x3) val result = f.s(Nil) println("log = " + result._1.reverse) log = List(2 + 2 = 4, 4 - 5 = -1, -1 + 2 = 1) println("result = " + result._2) result = 1
19. State Monad – No Sugar val f = add(2,2).flatMap{ x1 => sub(x1, 5).flatMap { x2 => add(x2,2) } }.map(identity) val result = f.s(Nil) println("log = " + result._1.reverse) log = List(2 + 2 = 4, 4 - 5 = -1, -1 + 2 = 1) println("result = " + result._2) result = 1
20. AST Evaluator - State object StateEvaluator extends EvaluatorTrait[Term, State[Int, Option[Int]]] { def eval(term: Term) = term match { case Constant(x) => State((s:Int) => (s + x, Some(x))) case Divide(a,b) => for ( evala <- eval(a); evalb <- eval(b)) yield OptionDivide(evala, evalb) } println(eval(Divide(Divide(Constant(1972),Constant(2)), Constant(23))).s(0)) (1997,Some(42)) println(eval(Divide(Constant(20),Constant(0))).s(0)) (20,None)
21. Summary Scala supports monadic style of computation to emulate features of “purer” functional programming languages For comprehensions imitate the functionality Haskell “do notation” Monadic computations can hide a lot of implementation details from those using them. Failures using Option State such as logging using the State monad.
23. Links James Iry – “Monads are Elephants” http://james-iry.blogspot.com/2007/09/monads-are-elephants-part-1.html http://james-iry.blogspot.com/2007/10/monads-are-elephants-part-2.html http://james-iry.blogspot.com/2007/10/monads-are-elephants-part-3.html Philip Wadler’s Monad Papers http://homepages.inf.ed.ac.uk/wadler/topics/monads.html Brian Beckman Monad Videos http://channel9.msdn.com/shows/Going+Deep/Brian-Beckman-Dont-fear-the-Monads/ http://channel9.msdn.com/shows/Going+Deep/Brian-Beckman-The-Zen-of-Expressing-State-The-State-Monad/
Editor's Notes
I personally find the spacesuit metaphor the most helpful
Return == unit
Return == unit
flatMap for Scala sequences and lists implement the List Monad
No unit method is implemented here. Oftentimes constructors act has units. There are some high-level functions that can operate over different types of monads that often need a unit function Identity pretty much put the “astronaut in another suit”
State in this case sums the constants (kind of contrived)
State in this case sums the constants (kind of contrived)
State in this case sums the constants (kind of contrived)