grokking monads in scala

23
Grokking Monads in Scala St. Louis Lambda Lounge August 5, 2010 Tim Dalton Senior Software Engineer Object Computing Inc.

Upload: tim-dalton

Post on 07-May-2015

3.226 views

Category:

Technology


3 download

DESCRIPTION

Presentation slide from St. Louis Lambda Lounge presentation on August 5th 2010.

TRANSCRIPT

Page 1: Grokking Monads in Scala

Grokking Monads in Scala

St. Louis Lambda LoungeAugust 5, 2010

Tim DaltonSenior Software Engineer

Object Computing Inc.

Page 2: Grokking Monads in Scala

Monads Are…

Just a monoid in the category of endofunctors.

Like “duh”!

Page 3: Grokking Monads in Scala

Monads Are…

• A way to structure computations

• A strategy for combining computations into more complex computations

(credit: Julien Wetterwald)

Page 4: Grokking Monads in Scala

Monads Are…• In Haskell, two core functions

(>>=) :: m a -> (a -> m b) -> m breturn :: a -> m a

• Monad Laws

• Left Unit: (return a) >>= k = k a

• Right Unitm >>= (return) = m

• Associative m >>= (\a -> (k a) >>= (\b -> h b)) =

(m >>= (\a -> k a)) >>= (\b -> h b)

Page 5: Grokking Monads in Scala

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) >>= \x -> Just (5*7) >>= \y -> return (x-y)

• Should evaluate to: Just(-27)

Page 6: Grokking Monads in Scala

Scala "For comprehensions"for (i <- 1 to 5) yield iscala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5)

for (i <- 1 to 5 if i % 2 == 0) yield iscala.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)

Page 7: Grokking Monads in Scala

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)

Page 8: Grokking Monads in Scala

A Monadic Traitabstract 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.

Page 9: Grokking 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)

}

Page 10: Grokking Monads in Scala

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

Page 11: Grokking Monads in Scala

AST Evaluator – Identityobject 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

Page 12: Grokking Monads in Scala

Useful Monad - Optionsealed 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

Page 13: Grokking Monads in Scala

Usefulness of Optionval areaCodes = Map(

"Fenton" -> 636,

"Florissant" -> 314,

"Columbia" -> 573 )

val homeTowns = Map(

"Moe" -> "Columbia",

"Larry" -> "Fenton",

"Curly" -> "Florissant",

"Schemp" -> "St. Charles” )

def personAreaCode(person:String) =

for (homeTown <- homeTowns.get(person);

areaCode <- areaCodes.get(homeTown)) yield (areaCode)

Page 14: Grokking Monads in Scala

Usefulness of Optionprintln(personAreaCode("Moe"))

Some(573)

println(personAreaCode("Schemp"))

None

println(personAreaCode("Joe"))

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

Page 15: Grokking Monads in Scala

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

}

}

Page 16: Grokking Monads in Scala

AST Evaluator - Option

println(eval(Divide(Divide(Constant(1972),Constant(2)), Constant(23))))

Some(42)

println(eval(Divide(Constant(1),Constant(0))))

None

Page 17: Grokking Monads in Scala

“Wonkier” Monad – Stateobject 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)

})

}

Page 18: Grokking Monads in Scala

State Monadval 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

Page 19: Grokking Monads in Scala

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

Page 20: Grokking Monads in Scala

AST Evaluator - Stateobject 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)

Page 21: Grokking Monads in Scala

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.

Page 22: Grokking Monads in Scala

Discussion

Can monads ever be “mainstream” ?

Page 23: Grokking Monads in Scala

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/