[Scala] Option with map, flatten, and flatMap

mj park
3 min readDec 14, 2020

--

It has been couple months since I started using Scala, and I would like to keep track of the material I study, for both myself and readers. This post is a summary of the tutorial from this website, specifically this tutorial.

Option

In Scala, it is not optimal to use null to represent a missing value. We need to use the type Option instead. It can represent both the presence and absence of a value. With its companion class Some and None, we no longer have to do a null check with Option.

For example, with the following code, the function sqrt(number: Int) can return either an integer value or null. Therefore, a calling method getSqrt() needs to do a null check when retrieving a result.

def sqrt(number: Int): Double = {
if (number >= 0)
Math.sqrt(number)
else
null // return null for a negative number
}
def getSqrt(number :Int): Double = {
val result = sqrt(number)
if (result == null) return 0 // null check here
result
}

Using Option, this can turn into the following.

  • If the number is ≥0, return Some of the square root of the number
  • Else, return None
  • No null check is needed. Instead, it’s using Scala’s pattern matching to retrieve the value.
def sqrt(number: Int): Option[Double] = {
if (number >= 0) Some(Math.sqrt(number)) else None
}
def getSqrt(number: Int): Double = {
sqrt(number) match {
Some(res) => res
None => 0
}
}

Option with map

In Scala, map is a higher order function that takes a function f.

  • If an optional value is present, it applies the function f and returns a value wrapped with Some
  • else (i.e. if an optional value is absent), it returns None
// definition of map in Option class
def map[B](f: A => B): Option[B] =
if (isEmpty) None else Some(f(this.get))

Suppose you have the following case classes (this will also be used below in flatten and flatMap examples)

case class Car(model: String, owner: Option[Person], plate: Option[String])
case class Person(name: String, age: Int, license: Option[String])

Examples

Get the “optional” owner name of the car

def ownerName(car: Car): Option[String] = {
car.owner.map(p => p.name)
}
val person1 = Person("James", 30, Some("LICENSE_NUM"))
val car1 = Car("HONDA", None, None)
val car2 = Car("BMW", Some(person1), Some("PLATE_NUM"))
println(ownerName(car1)) // returns None
println(ownerName(car2)) // returns Some("James")

To see what’s happening in details,

  • car.owner is returning Option[Person]
  • car.owner.map(p => p.name) is applying the function p => p.name on Option[Person]
  • If that Person object is present as p1, it returns Some("James").
  • Else if the object was None, it will return None

Option with Flatten

On Option, flatten works as the following

  • if the applied Option is Some(x), it returns the inner instances x
  • else if applied Option is None, it returns None
// original definition in Option 
def
flatten[B](implicit ev: A <:< Option[B]): Option[B] =
if (isEmpty) None else ev(this.get)
// Perhaps a simpler way to look at flatten on Option is this
def flatten: Option[A] = {
this match {
case Some(value) => value
case None => None
}
}

Examples

With the case class with Car and Person ,

  • car.owner is Option[Person]
  • person.license is Option[String]
  • Thus, car.owner.license is Option[Option[String]]

We will write a short function that returns a license of the owner of the car in Option

def getLicenseOfCarOwner(car: Car): Option[String] = {
car.owner.map(_.license).flatten
}

To see what is happening in details,

car.owner  // Option[String] 
(e.g. Some(Person(James, 30, Some("1234")) )
car.owner.map(_.license) // Option[Option[String]]
(e.g. Some(Some("1234")) )
car.owner.map(_.license).flatten // Option[String]
(e.g. Some("1234") )

Option with flatMap

FlatMap is a higher order function on Option[A] that applies a function f , which returns an optional value.

  • if the optional value is present, it applies the function on it’s inner object and returns an optional value, and there is no need to wrap the result in an optional value.
  • else if an optional value is None, it simply returns None
def flatMap[B](f: A => Option[B]): Option[B] =
if (isEmpty) None else f(this.get)

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

Examples

// function that returns a value of owner's license, which is optionalcase class Car(model: String, owner: Option[Person], plate: Option[String])
case class Person(name: String, age: Int, license: Option[String])
def ownerLicense(car: Car): Option[String] = {
car.owner.flatMap(p => p.license)
}
def ownerLicense2(car: Car): Option[String] = {
// using map + flatten
car.owner.map(p => p.license).flatten
}

Simply put, flatMap is map followed by flatten that returns an optional value.

That’s it for now! I hope this can be helpful for someone else. There is definitely a room for improvement, but I will adjourn for now :) Happy programming for all!

--

--

mj park
mj park

Written by mj park

Software Engineer | Scala | Functional Programming

No responses yet