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 = Item
val option = Option(Item(1))

val increment: Item => Item = 
  (x: Item) => new Item(x.value + 1)

option.map(increment) //Option(Item(2))
None.map(increment) //None
class Option[T] {
  def map[U](f: T => U): Option[U]
}

T = Item
U = Item
val 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 = Grade
val 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 & flatten
val 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 = Item

For 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 fancy

Not 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 == m

Why?

  • 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