4.1

Implement all of the preceding functions on Option. As you implement each function, try to think about what it means and in what situations you’d use it. We’ll explore when to use each of these functions next. Here are a few hints for solving this exercise:

  • It’s fine to use pattern matching, though you should be able to implement all the functions besides map and getOrElse without resorting to pattern matching.
  • For map and flatMap, the type signature should be enough to determine the implementation.
  • getOrElse returns the result inside the Some case of the Option, or if the Option is None, returns the given default value.
  • orElse returns the first Option if it’s defined; otherwise, it returns the second Option.

Solution

import scala.{Option => _, Some => _, Either => _, _} // hide std library `Option`, `Some` and `Either`, since we are writing our own in this chapter

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

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

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

  def orElse[B>:A](ob: => Option[B]): Option[B] = this match {
    case None => ob
    case _ => this
  }

  def filter(f: A => Boolean): Option[A] = this match {
    case Some(a) if f(a) => this
    case _ => None
  }
}
case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]

Run

// map
object Solution extends App {
  val some: Option[Int] = Some(1)
  println("Some(1) map (+10): " + some.map(_ + 10))

  val none: Option[Int] = None
  println("None map (+10): " + none.map(_ + 10))
}

// getOrElse
object Solution extends App {
  val some: Option[Int] = Some(1)
  println("Some(1) getOrElse (100): " + some.getOrElse(100))

  val none: Option[Int] = None
  println("None getOrElse (100): " + none.getOrElse(100))
}

// flatMap
object Solution extends App {
  def f(a: Int): Option[Int] = Some(a * 10)

  val some: Option[Int] = Some(4)
  println("Some(4) flatMap (_ * 10): " + some.flatMap(f))

  val none: Option[Int] = None
  println("None flatMap (_ * 10): " + none.flatMap(f))
}

// orElse
object Solution extends App {
  val some: Option[Int] = Some(4)
  println("Some(4) orElse (40): " + some.orElse(Some(40)))

  val none: Option[Int] = None
  println("None orElse (40): " + none.orElse(Some(40)))
}

// filter
object Solution extends App {
  def f(a: Int): Boolean = a > 100

  val some400: Option[Int] = Some(400)
  println("Some(400) filter (> 100): " + some400.filter(f))

  val some40: Option[Int] = Some(40)
  println("Some(40) filter (> 100): " + some40.filter(f))

  val none: Option[Int] = None
  println("None orElse (40): " + none.filter(f))
}

Output

// map
Some(1) map (+10): Some(11)
None map (+10): None

// getOrElse
Some(1) getOrElse (100): 1
None getOrElse (100): 100

// flatMap
Some(4) flatMap (_ * 10): Some(40)
None flatMap (_ * 10): None

// orElse
Some(4) orElse (40): Some(4)
None orElse (40): Some(40)

// filter
Some(400) filter (> 100): Some(400)
Some(40) filter (> 100): None
None orElse (40): None