Lecture 4: Options

Presenter Notes

Imperative Error Handling

  • flagged arguments
  • sentinel values
  • exceptions

Presenter Notes

Flagged Arguments

def mean(xs: Seq[Double], empty: Double): Double =
  if (xs.isEmpty) empty
  else xs.sum / xs.length

Presenter Notes

Sentinel Values

def mean(xs: Seq[Double]): Double =
  if (xs.isEmpty) Double.NaN
  else xs.sum / xs.length

Presenter Notes

Exceptions

def mean(xs: Seq[Double]): Double =
  if (xs.isEmpty)
    throw new ArithmeticException("NaN")
  else xs.sum / xs.length

Presenter Notes

Exceptions are not type-safe or referentially transparent.

The type of mean, Seq[Double]) => Double tells us nothing about the fact that exceptions may occur, and the compiler will not force callers of mean to make a decision about how to handle those exceptions.

Presenter Notes

The mean function is an example of what’s called a partial function: it’s not defined for some inputs.

A function is typically partial because it makes some assumptions about its inputs that aren’t implied by the input types.

Presenter Notes

Failure is an Option

Option is a monadic container that may or may not hold something. It tells you that a function might not return what you’re asking for.

val numbers = Map("one" -> 1, "two" -> 2)
//numbers: Map(one -> 1, two -> 2)
numbers.get("two")
//res0: Option[Int] = Some(2)
numbers.get("three")
//res1: Option[Int] = None

Presenter Notes

Application: Computing a mean

def mean(xs: Seq[Double]): Option[Double] =
  if (xs.isEmpty) None
  else Some(xs.sum / xs.length)

Presenter Notes

Option itself is generic and has two subclasses: Some[T] or None

trait Option[+A] //base trait
case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]

Presenter Notes

Options are like Lists with at most a single element.

trait List[+A] //base trait
case class Cons[+A](head: A, tail: List[A]) extends List[A]
case object Nil extends List[Nothing]

Presenter Notes

Here is a toy implementation of Option:

trait Option[+A] {
  def unit[A](a: A): Option[A] = Some(a)
  def flatMap[B](f: A => Option[B]): Option[B] =
    this match {
      case None => None
      case Some(a) => f(a)
    }
  def map[B](f: A => B): Option[B] =
    flatMap(f andThen unit)
}
case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]

Presenter Notes

Here is a hard-coded version of map:

def map[B](f: A => B): Option[B] =
  this match {
    case None => None
    case Some(a) => Some(f(a))
  }

Presenter Notes

There are a number of 'non-proper' combinators for safely exiting the Option monad. Two examples:

Presenter Notes

def getOrElse[B>:A](default: => B): B =
  this match {
    case None => default
    case Some(a) => a
  }

Presenter Notes

Exercise

Implement flatMap using map & getOrElse.

Presenter Notes

Presenter Notes

def flatMap1[B](f: A => Option[B]): Option[B] =
  map(f) getOrElse None

Presenter Notes

Try

Try is a general-purpose function for converting from an exception-based API to a container-based API.

def Try[A](a: => A): Option[A] =
  try { Some(a) }
  catch { case e: Exception => None }

Presenter Notes

The argument here must be lazy, otherwise Try will not function.

The None returned is not informative about the exception thrown. Next lecture's type Either fixes this.

Presenter Notes

Note that the Scala std lib has a type constructor Try[_], but it is not monadic:

import scala.util.{Try, Success}
def verify(n: Int): Try[Int] =
  if (n == 0) sys.error("nope") else Success(n)
val x = Try(0).flatMap(verify)
//x: Try[Int] = Failure(RuntimeException: nope)
val y = verify(0)
//RuntimeException: nope

Presenter Notes

We'll see another way to wrap a Try when we discuss the Validated applicative:

def fromTry[A](t: Try[A]): Validated[Throwable, A] =
  t match {
    case Failure(e) => invalid(e)
    case Success(v) => valid(v)
}
def fromEither[A, B](e: Either[A, B]): Validated[A, B] =
  e.fold(invalid, valid)
def fromOption[A, B](o: Option[B], ifNone: => A): Validated[A, B] =     
  o.fold(invalid[A, B](ifNone))(valid)

Presenter Notes

Option in Cats

import cats.syntax.option._
Some(3)
//res0: Some[Int] = Some(3)
3.some
//res1: Option[Int] = Some(3)
3.some.map { (i: Int) => i+2 }
//res2: Option[Int] = Some(5)

Presenter Notes

Reduction of for loops

for {
  i <- List(0)
} yield(i + 1)
//res0: List[Int] = List(1)
List(0) map {i => i + 1}
//res1: List[Int] = List(1)

Presenter Notes

for {
  i <- List(1)
  j <- List(2)
} yield (i, j)
//res2: List[(Int, Int)] = List((1,2))
List(1) flatMap {
  i => List(2) map {
    j => (i, j)
  }
}
//res3: List[(Int, Int)] = List((1,2))

Presenter Notes

'Looping' over Options

for {
  x <- 3.some
  y <- 4.some
} yield x+y
//res0: Option[Int] = Some(7)
3.some.flatMap { x =>
  4.some.map { y => x+y }
}           
//res1: Option[Int] = Some(7)

Presenter Notes

val error = none
for {
  x <- 3.some
  y <- error
} yield (x,y)
//res0: Option[(Int, Nothing)] = None

Presenter Notes

for {
  i <- List(1)
  j <- List()
} yield (i,j)
//res1: List[(Int, Nothing)] = List()

Presenter Notes

Exercise

Implement a map2 function with the following signature:

def map2[A,B,C](a: Option[A], b: Option[B])
  (f: (A, B) => C): Option[C]

Presenter Notes

Presenter Notes

def map2[A,B,C](a: Option[A], b: Option[B])
   (f: (A, B) => C): Option[C] =
  a flatMap (aa => b map (bb => f(aa, bb)))

Presenter Notes

We could also implement map2 with a for-comprehension:

def map2[A,B,C](a: Option[A], b: Option[B])
  (f: (A, B) => C): Option[C] =   
  for {
    i <- a
    j <- b
  } yield f(i, j)

Presenter Notes

Exercise

Provided a function mean,

mean(xs: Seq[Double]): Option[Double] = ...

implement a variance function with the following signature:

variance(xs: Seq[Double]): Option[Double]

Presenter Notes

Presenter Notes

def variance(xs: Seq[Double]): Option[Double] =
  mean(xs) flatMap { m =>
    mean(xs.map(x => math.pow(x - m, 2)))
  }

Presenter Notes

sequence and traverse

These are two combinators we will see repeatedly throughout the course:

def sequence[A](a: List[Option[A]]): Option[List[A]]
def traverse[A, B](a: List[A])(f: A => Option[B]): Option[List[B]]

Presenter Notes

sequence(List(Some(1), Some(2)))
//res0: Option[List[Int]] = Some(List(1, 2))
sequence(List(Some(1), None))
//res1: Option[List[Int]] = None

Presenter Notes

def foo(x: Int) = if (x==2) None else Some(x)
traverse(List(1,2,3))(foo)
//res2: Option[List[Int]] = None
traverse(List(1,3,4))(foo)
//res3: Option[List[Int]] = Some(List(1, 3, 4))

Presenter Notes

Exercise

Implement sequence[A](a: List[Option[A]]): Option[List[A]].

Presenter Notes

Presenter Notes

def sequence[A](a: List[Option[A]]):
  Option[List[A]] =
    a.foldRight[Option[List[A]]]
      (Some(Nil))
      ((x,y) => map2(x,y)(_ :: _))

Presenter Notes

def sequence1[A](a: List[Option[A]]):
  Option[List[A]] =
    a match {
      case Nil => Some(Nil)
      case h :: t => h flatMap (hh => sequence1(t) map (hh :: _))
    }

Presenter Notes

traverse can be trivially implemented with sequence and map.

def traverse[A, B](a: List[A])(f: A => Option[B]):
  Option[List[B]] =
    sequence a.map(f)

However this implementation is inefficient.

Presenter Notes

Exercise

Reimplement traverse so that it traverses the list only once.

Presenter Notes

Presenter Notes

def traverse[A, B] (a: List[A])(f: A => Option[B]):
  Option[List[B]] =
    a.foldRight[Option[List[B]]] (Some(Nil)) {
      (h,t) => map2(f(h),t)(_ :: _)
    }

Presenter Notes

def traverse1[A, B] (a: List[A])(f: A => Option[B]):
  Option[List[B]] =
    a match {
      case Nil => Some(Nil)
      case h :: t => f(h) flatMap {
        x: B => traverse(t)(f) map { xt => x :: xt }
      }
    }

Presenter Notes

def traverse2[A, B] (a: List[A])(
  f: A => Option[B]): Option[List[B]] =
  a match {
    case Nil => Some(Nil)
    case h :: t => traverse(t)(f) flatMap { xt =>
      f(h) map { x: B => x :: xt }
    }
  }

Presenter Notes

The fact that traverse1 and traverse2 are equivalent is a simple result of the Monad laws.

Note also that traverse is not equivalent to the other two, it uses only map2 and is therefore an applicative functor.

We'll discuss this more in the weeks to come.

Presenter Notes

Homework

Have a look at Xor and Validated in Cats.

Presenter Notes

Presenter Notes