Grokking Monads in Scala

Preview:

DESCRIPTION

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

Citation preview

Grokking Monads in Scala

St. Louis Lambda LoungeAugust 5, 2010

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

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)

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)

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)

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.

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

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

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)

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

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

})

}

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

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

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)

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/