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 returningOption[Person]
car.owner.map(p => p.name)
is applying the functionp => p.name
onOption[Person]
- If that Person object is present as
p1
, it returnsSome("James")
. - Else if the object was
None
, it will returnNone
Option with Flatten
On Option, flatten
works as the following
- if the applied
Option
is Some(x), it returns the inner instancesx
- else if applied
Option
isNone
, it returnsNone
// 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
isOption[Person]
person.license
isOption[String]
- Thus,
car.owner.license
isOption[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!