Slides for the presentation that I made for LambdaConf 2015 in Boulder
Codebase available at http://www.slideshare.net/paulszulc/monads-asking-the-right-question
21. 1. We understand code
2. It is easier to talk about related ideas and
then abstract to something more general
22. 1. We understand code
2. It is easier to talk about related ideas and
then abstract to something more general
3. Having abstract model created, we tend to
create metaphors
23. 1. We understand code
2. It is easier to talk about related ideas and
then abstract to something more general
3. Having abstract model created, we tend to
create metaphors
4. Having all above we can formalize
28. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
29. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String =
30. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
31. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1")
val university = WorkPlace("University", "Academic St. 10")
32. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1")
val university = WorkPlace("University", "Academic St. 10")
var penny = Geek("Penny", workPlace = cheeseCakeFactory)
var leonard = Geek("Leonard", workPlace = university)
33. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1")
val university = WorkPlace("University", "Academic St. 10")
var penny = Geek("Penny", workPlace = cheeseCakeFactory)
var leonard = Geek("Leonard", workPlace = university)
penny = penny.copy(partner = leonard)
leonard = leonard.copy(partner = penny)
34. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1")
val university = WorkPlace("University", "Academic St. 10")
var penny = Geek("Penny", workPlace = cheeseCakeFactory)
var leonard = Geek("Leonard", workPlace = university)
penny = penny.copy(partner = leonard)
leonard = leonard.copy(partner = penny)
println(partnerLookup(leonard))
println(partnerLookup(penny))
35. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1")
val university = WorkPlace("University", "Academic St. 10")
var penny = Geek("Penny", workPlace = cheeseCakeFactory)
var leonard = Geek("Leonard", workPlace = university)
penny = penny.copy(partner = leonard)
leonard = leonard.copy(partner = penny)
println(partnerLookup(leonard)) // output: "Cake Street 1"
println(partnerLookup(penny))
36. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1")
val university = WorkPlace("University", "Academic St. 10")
var penny = Geek("Penny", workPlace = cheeseCakeFactory)
var leonard = Geek("Leonard", workPlace = university)
penny = penny.copy(partner = leonard)
leonard = leonard.copy(partner = penny)
println(partnerLookup(leonard)) // output: "Cake Street 1"
println(partnerLookup(penny)) // output: "Academic St. 10"
41. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
42.
43. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
44. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
val university = WorkPlace("University", "Academic St. 10")
45. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
val university = WorkPlace("University", "Academic St. 10")
val rajesh = Geek("Rajesh", workPlace = university)
46. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
val university = WorkPlace("University", "Academic St. 10")
val rajesh = Geek("Rajesh", workPlace = university)
println(partnerLookup(rajesh))
47. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
val university = WorkPlace("University", "Academic St. 10")
val rajesh = Geek("Rajesh", workPlace = university)
println(partnerLookup(rajesh))
// java.lang.NullPointerException
// at wtf.examples.A1$.partnerLookUp(A1.scala:14)
// at ...
48. case class Geek(name: String, partner: Geek = null, workPlace: WorkPlace)
case class WorkPlace(name: String, street: String)
def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
val university = WorkPlace("University", "Academic St. 10")
val rajesh = Geek("Rajesh", workPlace = university)
println(partnerLookup(rajesh))
// java.lang.NullPointerException
// at wtf.examples.A1$.partnerLookUp(A1.scala:14)
// at ...
61. sealed trait Maybe[+A]
case class Some[+A](value: A) extends Maybe[A]
case object None extends Maybe[Nothing]
62. sealed trait Maybe[+A]
case class Some[+A](value: A) extends Maybe[A]
case object None extends Maybe[Nothing]
object Maybe {
def apply[A](value: A) = Some(value)
}
63. sealed trait Maybe[+A] {
}
object Maybe {
def apply[A](value: A) = Some(value)
}
case class Some[+A](value: A) extends Maybe[A] {
}
case object None extends Maybe[Nothing] {
}
64. sealed trait Maybe[+A] {
def andThen[B](f: A => Maybe[B]): Maybe[B]
}
object Maybe {
def apply[A](value: A) = Some(value)
}
case class Some[+A](value: A) extends Maybe[A] {
}
case object None extends Maybe[Nothing] {
}
65. sealed trait Maybe[+A] {
def andThen[B](f: A => Maybe[B]): Maybe[B]
}
object Maybe {
def apply[A](value: A) = Some(value)
}
case class Some[+A](value: A) extends Maybe[A] {
def andThen[B](f: A => Maybe[B]): Maybe[B] = f(value)
}
case object None extends Maybe[Nothing] {
}
66. sealed trait Maybe[+A] {
def andThen[B](f: A => Maybe[B]): Maybe[B]
}
object Maybe {
def apply[A](value: A) = Some(value)
}
case class Some[+A](value: A) extends Maybe[A] {
def andThen[B](f: A => Maybe[B]): Maybe[B] = f(value)
}
case object None extends Maybe[Nothing] {
def andThen[B](f: Nothing => Maybe[B]): Maybe[B] = this
}
67. case class Geek(name: String, partner: Maybe[Geek] = wtf.monads.None, workPlace: Maybe[WorkPlace])
case class WorkPlace(name: String, street: Maybe[String])
def partnerLookup(geek: Geek): String =
68. case class Geek(name: String, partner: Maybe[Geek] = wtf.monads.None, workPlace: Maybe[WorkPlace])
case class WorkPlace(name: String, street: Maybe[String])
def partnerLookup(geek: Geek): String =
geek.partner
69. case class Geek(name: String, partner: Maybe[Geek] = wtf.monads.None, workPlace: Maybe[WorkPlace])
case class WorkPlace(name: String, street: Maybe[String])
def partnerLookup(geek: Geek): String =
geek.partner.andThen(g => g.workPlace)
70. case class Geek(name: String, partner: Maybe[Geek] = wtf.monads.None, workPlace: Maybe[WorkPlace])
case class WorkPlace(name: String, street: Maybe[String])
def partnerLookup(geek: Geek): String =
geek.partner.andThen(g => g.workPlace).andThen(wp => wp.street)
71. case class Geek(name: String, partner: Maybe[Geek] = wtf.monads.None, workPlace: Maybe[WorkPlace])
case class WorkPlace(name: String, street: Maybe[String])
def partnerLookup(geek: Geek): String =
geek.partner.andThen(g => g.workPlace).andThen(wp => wp.street) match {
case wtf.monads.Some(street) => street
}
72. case class Geek(name: String, partner: Maybe[Geek] = wtf.monads.None, workPlace: Maybe[WorkPlace])
case class WorkPlace(name: String, street: Maybe[String])
def partnerLookup(geek: Geek): String =
geek.partner.andThen(g => g.workPlace).andThen(wp => wp.street) match {
case wtf.monads.Some(street) => street
case wtf.monads.None => "not found"
}
73. val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1")
val university = WorkPlace("University", "Academic St. 10")
var penny = Geek("Penny", workPlace = cheeseCakeFactory)
var leonard = Geek("Leonard", workPlace = university)
val rajesh = Geek("Rajesh", workPlace = university)
penny = penny.copy(partner = leonard)
leonard = leonard.copy(partner = penny)
println(partnerLookup(leonard))
println(partnerLookup(penny))
println(partnerLookup(rajesh))
74. val cheeseCakeFactory = WorkPlace("Cheese Cake Factory", "Cake Street 1")
val university = WorkPlace("University", "Academic St. 10")
var penny = Geek("Penny", workPlace = cheeseCakeFactory)
var leonard = Geek("Leonard", workPlace = university)
val rajesh = Geek("Rajesh", workPlace = university)
penny = penny.copy(partner = leonard)
leonard = leonard.copy(partner = penny)
println(partnerLookup(leonard)) // output: "Cake Street 1"
println(partnerLookup(penny)) // output: "Academic St. 10"
println(partnerLookup(rajesh)) // output: "not found"
75. def partnerLookup(geek: Geek): String =
geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match {
case wtf.monads.Some(street) => street
case wtf.monads.None => "not found"
}
76. def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
def partnerLookup(geek: Geek): String =
geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match {
case wtf.monads.Some(street) => street
case wtf.monads.None => "not found"
}
77. def partnerLookup(geek: Geek): String = geek.partner.workPlace.street
def partnerLookup(geek: Geek): String =
geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match {
case wtf.monads.Some(street) => street
case wtf.monads.None => "not found"
}
78. def partnerLookup(geek: Geek): String =
geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match {
case wtf.monads.Some(street) => street
case wtf.monads.None => "not found"
}
79. def partnerLookup(geek: Geek): String =
geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match {
case wtf.monads.Some(street) => street
case wtf.monads.None => "not found"
}
def partnerLookup(geek: Geek): String =
80. def partnerLookup(geek: Geek): Maybe[String] =
geek.partner.andThen(p => p.workPlace).andThen(wp => wp.street) match {
case wtf.monads.Some(street) => street
case wtf.monads.None => "not found"
}
def partnerLookup(geek: Geek): Maybe[String] =
103. case class Pirate(name: String, ships: Many[Ship])
case class Ship(name: String, hold: Hold)
case class Hold(barrel: Many[Barrel])
case class Barrel(amount: Int)
104. case class Pirate(name: String, ships: Many[Ship])
case class Ship(name: String, hold: Hold)
case class Hold(barrel: Many[Barrel])
case class Barrel(amount: Int)
val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
105. case class Pirate(name: String, ships: Many[Ship])
case class Ship(name: String, hold: Hold)
case class Hold(barrel: Many[Barrel])
case class Barrel(amount: Int)
val blackPearl = Ship("Black Pearl", blackHold)
val whitePearl = Ship("White Pearl", whiteHold)
val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
106. case class Pirate(name: String, ships: Many[Ship])
case class Ship(name: String, hold: Hold)
case class Hold(barrel: Many[Barrel])
case class Barrel(amount: Int)
val blackHold = Hold(Empty)
val whiteHold = Hold(Many(Barrel(20), Barrel(10)))
val blackPearl = Ship("Black Pearl", blackHold)
val whitePearl = Ship("White Pearl", whiteHold)
val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
109. sealed trait Many[+A]
case class Const[+A](head: A, tail: Many[A]) extends Many[A]
case object Empty extends Many[Nothing]
110. sealed trait Many[+A]
case class Const[+A](head: A, tail: Many[A]) extends Many[A]
case object Empty extends Many[Nothing]
object Many {
def apply[A](elements: A*): Many[A] = {
if(elements.length == 1) Const(elements(0), Empty)
else Const(elements(0), apply(elements.drop(1) : _*))
}
}
111. sealed trait Many[+A] {
def andThen[B](f: A => Many[B]): Many[B]
def within[B](f: A => B): Many[B]
}
112. case class Const[+A](head: A, tail: Many[A]) extends Many[A] {
def andThen[B](f: A => Many[B]): Many[B] = ...
def within[B](f: A => B): Many[B] = ...
}
113. case class Const[+A](head: A, tail: Many[A]) extends Many[A] {
def andThen[B](f: A => Many[B]): Many[B] = ...
def within[B](f: A => B): Many[B] = Const(f(head), tail.within(f))
}
114. case class Const[+A](head: A, tail: Many[A]) extends Many[A] {
def andThen[B](f: A => Many[B]): Many[B] =
concat( f(head), tail.andThen(f) )
def within[B](f: A => B): Many[B] = Const(f(head), tail.within(f))
}
115. case class Const[+A](head: A, tail: Many[A]) extends Many[A] {
def andThen[B](f: A => Many[B]): Many[B] =
concat( f(head), tail.andThen(f) )
private def concat[A](first: Many[A],
second: Many[A]): Many[A] = { … }
def within[B](f: A => B): Many[B] = Const(f(head), tail.within(f))
}
116. case class Const[+A](head: A, tail: Many[A]) extends Many[A] {
def andThen[B](f: A => Many[B]): Many[B] =
concat( f(head), tail.andThen(f) )
private def concat[A](first: Many[A],
second: Many[A]): Many[A] = { … }
def within[B](f: A => B): Many[B] = Const(f(head), tail.within(f))
}
case object Empty extends Many[Nothing] {
def andThen[B](f: Nothing => Many[B]): Many[B] = this
def within[B](f: Nothing => B): Many[B] = this
}
117. val blackPearl = Ship("Black Pearl", Hold(Empty))
val whitePearl = Ship("White Pearl",
Hold(Many(Barrel(20), Barrel(10))))
val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
118. val blackPearl = Ship("Black Pearl", Hold(Empty))
val whitePearl = Ship("White Pearl",
Hold(Many(Barrel(20), Barrel(10))))
val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
jack.ships.andThen(ship => ship.hold.barrel)
.within(barrel => barrel.amount)
119. val blackPearl = Ship("Black Pearl", Hold(Empty))
val whitePearl = Ship("White Pearl",
Hold(Many(Barrel(20), Barrel(10))))
val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
jack.ships.andThen(ship => ship.hold.barrel)
.within(barrel => barrel.amount)
import Magic._
val amounts = for {
ship <- jack.ships
barrel <- ship.hold.barrel
} yield (barrel.amount)
120. val blackPearl = Ship("Black Pearl", Hold(Empty))
val whitePearl = Ship("White Pearl",
Hold(Many(Barrel(20), Barrel(10))))
val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
jack.ships.andThen(ship => ship.hold.barrel)
.within(barrel => barrel.amount)
import Magic._
val amounts = for {
ship <- jack.ships
barrel <- ship.hold.barrel
} yield (barrel.amount)
121. val blackPearl = Ship("Black Pearl", Hold(Empty))
val whitePearl = Ship("White Pearl",
Hold(Many(Barrel(20), Barrel(10))))
val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
jack.ships.andThen(ship => ship.hold.barrel)
.within(barrel => barrel.amount)
import Magic._
val amounts = for {
ship <- jack.ships
barrel <- ship.hold.barrel
} yield (barrel.amount)
122. val blackPearl = Ship("Black Pearl", Hold(Empty))
val whitePearl = Ship("White Pearl",
Hold(Many(Barrel(20), Barrel(10))))
val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
jack.ships.andThen(ship => ship.hold.barrel)
.within(barrel => barrel.amount)
import Magic._
val amounts = for {
ship <- jack.ships
barrel <- ship.hold.barrel
} yield (barrel.amount)
123. val blackPearl = Ship("Black Pearl", Hold(Empty))
val whitePearl = Ship("White Pearl",
Hold(Many(Barrel(20), Barrel(10))))
val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
jack.ships.andThen(ship => ship.hold.barrel)
.within(barrel => barrel.amount)
import Magic._
val amounts = for {
ship <- jack.ships
barrel <- ship.hold.barrel
} yield (barrel.amount)
124. val blackPearl = Ship("Black Pearl", Hold(Empty))
val whitePearl = Ship("White Pearl",
Hold(Many(Barrel(20), Barrel(10))))
val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
jack.ships.andThen(ship => ship.hold.barrel)
.within(barrel => barrel.amount)
import Magic._
val amounts = for {
ship <- jack.ships
barrel <- ship.hold.barrel
} yield (barrel.amount)
125. val blackPearl = Ship("Black Pearl", Hold(Empty))
val whitePearl = Ship("White Pearl",
Hold(Many(Barrel(20), Barrel(10))))
val jack = Pirate("Captain Jack Sparrow", Many(blackPearl, whitePearl))
jack.ships.andThen(ship => ship.hold.barrel)
.within(barrel => barrel.amount)
import Magic._
val amounts = for {
ship <- jack.ships
barrel <- ship.hold.barrel
} yield (barrel.amount)
println(amounts) // Const(20,Const(10,Empty))
128. ● We've seen two wrapping classes.
● All have andThen method.
129. ● We've seen two wrapping classes.
● All have andThen method.
● Each can take a function that can be called
on a value wrapped by the structure (which
can be empty or there can be multiple
instances of it).
133. Monad is a container, a box.
A box in which we can store
an object. Wrap it.
134. Monad is a container, a box.
A box in which we can store
an object, a value.
A box that has a common
interface, which allows us to
do one thing: connect
sequence of operations on the
content of the box.
135. Monad is a container, a box.
A box in which we can store
an object, a value.
A box that has a common
interface, which allows us to
do one thing: connect
sequence of operations on the
content of the box.
andThen means "do the next
things".
136. Monad is a container, a box.
A box in which we can store
an object, a value.
A box that has a common
interface, which allows us to
do one thing: connect
sequence of operations on the
content of the box.
andThen means "do the next
things".
But the way it is done depends
on a box.
137. Monad is a container, a box.
A box in which we can store
an object, a value.
A box that has a common
interface, which allows us to
do one thing: connect
sequence of operations on the
content of the box.
andThen means "do the next
things".
But the way it is done depends
on a box.
138. Monad is a container, a box.
A box in which we can store
an object, a value.
A box that has a common
interface, which allows us to
do one thing: connect
sequence of operations on the
content of the box.
andThen means "do the next
things".
But the way it is done depends
on a box.
139. Monad is a container, a box.
A box in which we can store
an object, a value.
A box that has a common
interface, which allows us to
do one thing: connect
sequence of operations on the
content of the box.
andThen means "do the next
things".
But the way it is done depends
on a box.
142. Monad is an abstract data type.
It has two operators: andThen and unit
143. Monad is an abstract data type.
It has two operators: andThen and unit
object Maybe {
def apply[A](value: A) = Some(value)
}
144. Monad is an abstract data type.
It has two operators: andThen and unit
145. Monad is an abstract data type.
It has two operators: andThen and unit
146. Monad is an abstract data type.
It has two operators: andThen and unit
bind (>>=) // formal name
147. Monad is an abstract data type.
It has two operators: andThen and unit
bind (>>=) // formal name
flatMap // in Scala
148. def partnerLookup(geek: Geek): Maybe[String] = {
import Magic._
for {
p <- geek.partner
wp <- p.workPlace
s <- wp.street
} yield (s)
}
149. def partnerLookup(geek: Geek): Maybe[String] = {
import Magic._
for {
p <- geek.partner
wp <- p.workPlace
s <- wp.street
} yield (s)
}
150. object Magic {
case class RichMaybe[A](m: Maybe[A]) {
def flatMap[B](f: A => Maybe[B]): Maybe[B] = m.andThen(f)
def map[B](f: A => B): Maybe[B] = m.within(f)
}
implicit def enrich[A](m: Maybe[A]) = RichMaybe(m)
}
151. def partnerLookup(geek: Geek): Maybe[String] = {
import Magic._
for {
p <- geek.partner
wp <- p.workPlace
s <- wp.street
} yield (s)
}
152. def partnerLookup(geek: Geek): Maybe[String] = {
for {
p <- geek.partner
wp <- p.workPlace
s <- wp.street
} yield (s)
}
153. Monad is an abstract data type.
It has two operators: bind and unit
154. Monad is an abstract data type.
It has two operators: bind and unit
Those operators must follow certain rules:
1. associativity
2. left identity
3. right identity
155. 1. associativity
2. left identity
3. right identity
unit acts approximately as a
neutral element of bind
156. 1. associativity
2. left identity
3. right identity
unit acts approximately as a
neutral element of bind
unit allows us to put value
into the box without
damaging it
157. 1. associativity
2. left identity
3. right identity
unit acts approximately as a
neutral element of bind
unit(x).flatMap(f) == f(x)
m.flatMap(unit) == m
unit allows us to put value
into the box without
damaging it
158. 1. associativity
2. left identity
3. right identity
“Binding two functions in
succession is the same as
binding one function that can
be determined from them”
159. 1. associativity
2. left identity
3. right identity
m.flatMap{f(_)}.flatMap.{g(_)}
==
m.flatMap{f(_).flatMap.{g(_)}}
“Binding two functions in
succession is the same as
binding one function that can
be determined from them”