Monads
practical applications
class Item(val value: Int)
def firstPositiveNumber(collection: List[Item]): Item = {
for (number <- collection) {
if (number.value > 0) {
return number
}
}
null
}def printFirstPositive(collection: List[Item]) = {
println(firstPositiveNumber(collection).value)
}you would think, right?
def printFirstPositive(collection: List[Item]) = {
val number = firstPositiveNumber(collection)
if (number) {
println(number.value)
}
}better!
x 100 different callers

Option pattern



abstract class Option[+T]
case class Some[+T](x: T) extends Option[T]
case object None extends Option[Nothing]def firstPositiveNumber(collection: List[Item]): Option[Item] = {
for (number <- collection) {
if (number.value > 0) {
return Some(number)
}
}
None
}def printFirstPositive(collection: List[Item]) = {
val maybeNumber = firstPositiveNumber(collection)
maybeNumber match {
case Some(x) => println(x.value)
case None =>
}
}Why?
- works with primitive types (vs. null)
- defend agains NPE
- explicit - method can return nothing
-
type safe
- NPE becomes compile error
- composability...
Functional Programming Recap
val collection = List(new Item(1), new Item(2))
val increment: Item => Item =
(x: Item) => new Item(x.value + 1)
collection.map(increment) //List(Item(2), Item(3))class List[T] {
def map[U](f: T => U): List[U]
}
T = Item
U = Itemval option = Option(Item(1))
val increment: Item => Item =
(x: Item) => new Item(x.value + 1)
option.map(increment) //Option(Item(2))
None.map(increment) //Noneclass Option[T] {
def map[U](f: T => U): Option[U]
}
T = Item
U = Itemval collection = List(new Item(1), new Item(2))
val anotherCollection = List(new Item(3), new Item(4))
val inception = List(collection, anotherCollection)
collection.flatten //List(Item(1), Item(2), Item(3), Item(4))class List[T] {
def flatMap[U](f: T => List[U]): List[U]
}
T = Student
U = Gradeval students = List(new Student(1), new Student(2))
//List of students
val gradesForStudent = students.map(student => gradesFor(student))
//List of List of grades

val allGrades = gradesForStudent.flatten

//List of grades
//or shorter
val allGrades = students.flatMap(student => gradesFor(student))
// flatMap = map & flattenval maybeItem = Option(new Item(1))
val maybeIncrement: Item => Option[Item] =
(x: Item) => Some(new Item(x.value + 1))
maybeItem.flatMap(maybeIncrement) //Option(2)class Option[T] {
def flatMap[U](f: T => Option[U]): Option[U]
}
T = Item
U = ItemFor Comprehension
Case Study: Carthesian Product
val firstCollection = [1, 2]
val secondCollection = [3, 4]
val carthesianProd = [(1, 3), (1, 4), (2, 3), (2, 4)]firstCollection.map( first =>
secondCollection.map ( second =>
(first, second)
).flatten
)firstCollection.flatMap( first =>
secondCollection.map ( second =>
(first, second)
)
)for {
first <- firstCollection,
second <- secondCollection
} yield (first, second)Ideally
for {
first <- firstCollection,
second <- secondCollection
} yield (first, second)Ideally
why not?
for {
positive <- firstPositiveNumber(collection),
sqrt <- tryToExtractSqrt(positive)
fancy <- tryToDoSomethingFancy(sqrt)
} yield fancyNot only for collections!
all you need is map & flatMap
abstract class Option[+T] {
def flatMap[U](f: T => Option[U]): Option[U] =
this match {
case Some(x) => f(x)
case None => None
}
def map[U](f: T => U): Option[U] =
this match {
case Some(x) => Some(f(x))
case None => None
}
}Why?
- abstract flow control
- no more if
- hide the pattern match
- reads better :)
Where can this be applied?
Promises
//gimme student with courses and their sections
var theStudent
loadStudent()
.then(student => {
theStudent = student
loadCourses(student)
})
.then(courses => {
theStudent.courses = courses
courses.map(course =>
loadSection(course)
.then(sections => course.sections = sections)
)
})
.then(_ => theStudent)//gimme student with courses and their sections
for {
student <- loadStudent()
courses <- loadCourses(student)
course <- courses
sections <- loadSections(course)
} {
student.courses ||= []
student.courses += course
course.sections = sections
}Monads
trait Monad[T] {
def flatMap[U](f: T => Monad[U]): Monad[U]
def unit[T](x: T): M[T]
//nice to have
def map[U](f: T => U): Monad[U]
// = this.flatMap(x => unit(f(x))
}- List, Set, ...
- Option
- Generator
Monad Laws
Associativity
(m flatMap f) flatMap g == m flatMap (x => f(x) flatMap g)
Left Unit
unit(x) flatMap f == f(x)
Right Unit
m flatMap unit == mWhy?
- they make the code pretty
- high power of abstractions
- used in a bunch of places
- Option, Promise, Rx, etc.
- create your own monads
Q & A
Thanks!
Resources
- https://www.coursera.org/specializations/scala - Functional Programming Design
- http://www.meetup.com/Big-Data-Timisoara-Romania/events/228863680/ - Lorand Szakacs
- https://en.wikipedia.org/wiki/Monad_(functional_programming)
Option Pattern
By Horia Radu
Option Pattern
- 290