[Scala] Option with map, flatten, and flatMap
By: Mj Park
Tags: Scala, Scalaoption

It has been couple months since I started using Scala, and I would like to record the material I study for both myself and audiences. This short writing is somewhat a summary of the tutorial from this website (https://freecontent.manning.com/), specifically this tutorial https://freecontent.manning.com/using-option-in-scala-part-2-map-and-flatmap/

Option

In Scala, it is not optimal to use null to represent missing values. 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

} def getSqrt(number :Int): Double = {

val result = sqrt(number)

if (result == null) return 0

result

}

Using Option, this can turn into the following

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 p1 = Person("James", 30, Some("LICENSE_NUM"))

val car1 = Car("HONDA", None, None)

val car2 = Car("BMW", Some(p1), Some("PLATE_NUM")) println(ownerName(car1)) // returns None

println(ownerName(car2)) // returns Some("James")

To see what’s happening in details,

car.owner = returns Option[Person]

= returns Option[Person] car.owner.map(p => p.name) = applies the function p => p.name on Option[Person]. Assuming that Person object is present as p1 , it returns Some(“James”)

Option with Flatten

On Option, flatten works as the following

if applied optional value is present (i.e. Some(x)), it returns the inner instances x

if applied optional value is absent (i.e. 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

def flatten: Option[A] = {

this match {

case Some(value) => value

case None => None

}

}

Examples

def getOwnerLicense(car: Car): Option[String] = {

car.owner.map(_.license).flatten

}

// to see what's happening in details

car.owner // Option[String]

car.owner.map(_.license) // Option[Option[String]]

car.owner.map(_.license).flatten // Option[String]

Option with flatMap

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

if an optional value is present, it applies the function on it’s inner object and returns an optional value

if an optional value is absent, 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 optional case 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

}

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!