Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
Grokking Monads in Scala St. Louis Lambda Lounge August 5, 2010 Tim Dalton Senior Software Engineer Object Computing Inc.
Monads Are… Just a monoid in the category of endofunctors.  Like “duh”!
Monads Are… A way to structure computations A strategy for combining computations into more complex computations (credit: Julien Wetterwald)
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)
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)
Scala &quot;For comprehensions&quot; 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 + &quot; &quot; ) } 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)
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 + &quot; &quot; ) } 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)
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.
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) }
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
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 &quot;main&quot; java.lang.ArithmeticException: / by zero
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(&quot;None.get&quot;) } Also referred to as the Maybe or Failure monad. Haskell supports Just/Nothing that correlates to Some/None in Scala
Usefulness of Option val areaCodes = Map( &quot;Fenton&quot; -> 636, &quot;Florissant&quot; -> 314, &quot;Columbia&quot; -> 573 ) val homeTowns = Map( &quot;Moe&quot; -> &quot;Columbia&quot;, &quot;Larry&quot; -> &quot;Fenton&quot;, &quot;Curly&quot; -> &quot;Florissant&quot;,  &quot;Schemp&quot; -> &quot;St. Charles” ) def personAreaCode(person:String) =  for (homeTown <- homeTowns.get(person);   areaCode <- areaCodes.get(homeTown)) yield (areaCode)
Usefulness of Option println(personAreaCode(&quot;Moe&quot;)) Some(573) println(personAreaCode(&quot;Schemp&quot;)) None println(personAreaCode(&quot;Joe&quot;)) None println( for (areaCode <- areaCodes if areaCode._2 == 314; stoogeHome <- homeTowns if stoogeHome._2 == areaCode._1) yield stoogeHome._1 ) List(Curly) Look Mom, No null checks !!!
AST Evaluator - Option object OptionDivide  extends ((Option[Int], Option[Int]) => Option[Int]) { def apply(a:Option[Int], b:Option[Int]) = for (bp <- b; ap <- if (bp != 0) a else None) yield (ap/bp) } object OptionEvaluator extends EvaluatorTrait[Term, Option[Int]] { def eval(term: Term) = term match { case Constant(x) => Some(x) case Divide(a,b) => OptionDivide(eval(a), eval(b)) } }
AST Evaluator - Option println(eval(Divide(Divide(Constant(1972),Constant(2)), Constant(23)))) Some(42) println(eval(Divide(Constant(1),Constant(0)))) None
“ 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) }) }
State Monad val add = (x:Int, y:Int) =>  State[List[String], Int]((s:List[String]) => { ((x + &quot; + &quot; + y + &quot; = &quot; + (x + y)) :: s, (x + y))  }) val sub = (x:Int, y:Int) =>  State[List[String], Int]((s:List[String]) => {    ((x + &quot; - &quot; + y + &quot; = &quot; + (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(&quot;log = &quot; + result._1.reverse) log = List(2 + 2 = 4, 4 - 5 = -1, -1 + 2 = 1) println(&quot;result = &quot; + result._2) result = 1
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(&quot;log = &quot; + result._1.reverse) log = List(2 + 2 = 4, 4 - 5 = -1, -1 + 2 = 1) println(&quot;result = &quot; + result._2) result = 1
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)
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.
Discussion  Can monads ever be “mainstream” ?
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/

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 &quot;For comprehensions&quot; 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 + &quot; &quot; ) } 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 + &quot; &quot; ) } 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 &quot;main&quot; 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(&quot;None.get&quot;) } Also referred to as the Maybe or Failure monad. Haskell supports Just/Nothing that correlates to Some/None in Scala
  • 13. Usefulness of Option val areaCodes = Map( &quot;Fenton&quot; -> 636, &quot;Florissant&quot; -> 314, &quot;Columbia&quot; -> 573 ) val homeTowns = Map( &quot;Moe&quot; -> &quot;Columbia&quot;, &quot;Larry&quot; -> &quot;Fenton&quot;, &quot;Curly&quot; -> &quot;Florissant&quot;, &quot;Schemp&quot; -> &quot;St. Charles” ) def personAreaCode(person:String) = for (homeTown <- homeTowns.get(person); areaCode <- areaCodes.get(homeTown)) yield (areaCode)
  • 14. Usefulness of Option println(personAreaCode(&quot;Moe&quot;)) Some(573) println(personAreaCode(&quot;Schemp&quot;)) None println(personAreaCode(&quot;Joe&quot;)) None println( for (areaCode <- areaCodes if areaCode._2 == 314; stoogeHome <- homeTowns if stoogeHome._2 == areaCode._1) yield stoogeHome._1 ) List(Curly) Look Mom, No null checks !!!
  • 15. AST Evaluator - Option object OptionDivide extends ((Option[Int], Option[Int]) => Option[Int]) { def apply(a:Option[Int], b:Option[Int]) = for (bp <- b; ap <- if (bp != 0) a else None) yield (ap/bp) } object OptionEvaluator extends EvaluatorTrait[Term, Option[Int]] { def eval(term: Term) = term match { case Constant(x) => Some(x) case Divide(a,b) => OptionDivide(eval(a), eval(b)) } }
  • 16. AST Evaluator - Option println(eval(Divide(Divide(Constant(1972),Constant(2)), Constant(23)))) Some(42) println(eval(Divide(Constant(1),Constant(0)))) None
  • 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 + &quot; + &quot; + y + &quot; = &quot; + (x + y)) :: s, (x + y)) }) val sub = (x:Int, y:Int) => State[List[String], Int]((s:List[String]) => { ((x + &quot; - &quot; + y + &quot; = &quot; + (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(&quot;log = &quot; + result._1.reverse) log = List(2 + 2 = 4, 4 - 5 = -1, -1 + 2 = 1) println(&quot;result = &quot; + 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(&quot;log = &quot; + result._1.reverse) log = List(2 + 2 = 4, 4 - 5 = -1, -1 + 2 = 1) println(&quot;result = &quot; + 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.
  • 22. Discussion Can monads ever be “mainstream” ?
  • 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

  1. I personally find the spacesuit metaphor the most helpful
  2. Return == unit
  3. Return == unit
  4. flatMap for Scala sequences and lists implement the List Monad
  5. 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”
  6. State in this case sums the constants (kind of contrived)
  7. State in this case sums the constants (kind of contrived)
  8. State in this case sums the constants (kind of contrived)