Commit Logs

Scala Option, Some and None

Almost every programming languages have certain mechanism to deal with the null values, i.e. the absence of some values, in order to work safely in the messy real world. Many languages choose to have a separate object for these absent values, e.g. the null in Java. And defensive programming teaches us to handle this scenario separately, given whether it is a value that is returned or it is the null object. So, you might find the this description familiar: the null checkers are placed all over the codebase and specific logics are needed to handle each one of them.

But Scala thinks different. It handles the null values in a very elegent way via the Option class. Read on if you are tired of those null checkers like I am.

Some basics

In short, if you have a value of type A that may be absent, Scala uses an instance of Option[A] as its container. An Intance of Option is either an instance of case class Some when it is present or case object None when it is not. Since both Some and None are children of Option, your function signature should declare that the returned value is an Option of some type, e.g. Option[A] in the above example. In that way, the users of that function are forced by the complier to handle the possibility of absence, which follows the Principle of Least Astonishment.

It is very easy to create an Option in Scala, i.e. you can use a present/absent value directly.

1
2
3
val optionalInt: Option[Int] = Some(1)
// or
// val optionalInt: Option[Int] = None

Or, if you know a function may not return a value, you could specify its return value as Option.

1
def foo: Option[String] = { ... }

Similarly, you can write Option[A] anywhere you think a value of type A may be absent.

1
2
3
def foo(param: Option[A]): ...

case class Foo(param1: Option[A], param2: Int)

Working with Option

Hopefully you are sold by now that the concept of Option is pretty intuitive and neat. In that case, you are probably already eager to know how to actually work with it. Well, just like almost everything in Scala, there are many ways to work with Option. For better or worse, Scala is optimized for flexibility and easy writability. Now let me walk you through some of the more common approaches.

isDefined

This approach should be quite familiar to you. It is very similar to what you would do for languages with a separate null object. That if, you add a checker for the None value using the isDefined method and specify logic to handle each scenario accordingly.

Suppose we need a function addTwo that takes an integer value, which might be absent (treats the input as zero if not present), and adds it by two, the function below would meet the requirements. Note that, if the input is present, i.e. a.isDefined, we use the get method to get its value.

1
2
3
4
5
6
def addTwoWithDefault(a: Option[Int]): Int = {
if(a.isDefined) a.get + 2 else 2
}

addTwoWithDefault(5) // res1: Int = 7
addTwoWithDefault(None) // res1: Int = 2

pattern matching

Another similar approach is to use pattern matching, given Some is a case class. I can rewrite the addTwo function in the following way with pattern matching.

1
2
3
4
5
6
def addTwoWithDefault(a: Option[Int]): Int = {
a match {
case None => 2
case _ => a.get + 2
}
}

getOrElse

In many cases, you have a fallback or default value for your absent values, e.g. zero in the above example. With Option, you can easily provide a default value via the getOrElse method.

1
def addTwoWithDefault(a: Option[Int]): Int = a.getOrElse(0) + 2

One thing to keep in mind is that getOrElse doesn’t preserve the type of the Option and you should be cautious to specify the type of the default value.

1
getOrElse[B >: A](default: ⇒ B): B

The above approaches may not look better than dealing with null in Java. And, you might have guessed it already, these use cases are not really where Scala Option shines. Now, let’s look at some of the more idiomatic way to process Option in Scala.

flatten

For similicity, assume that we have a List of Option[Int].

1
val l: List[Option[Int]] = List(Some(3), Some(1), None, Some(5), Some(8), None)

A common scenario is that we need to filter out the absent values and return a List of Int. A straightfoward approach is to combine filter with .isDefined.

1
2
l.filter(_.isDefined).map(_.get) 
// res1: List[Int] = List(3, 1, 5, 8)

However, Scala actually provides an elegent built-in function to achieve the same goal, which is often more preferred.

1
2
l.flatten
// res1: List[Int] = List(3, 1, 5, 8)

flatMap

Along the same line, suppose we want to add two to each of the present value in the List and calculate their summation, we can use flatMap to get there easily.

1
2
3
4
5
6
def addTwo(a: Option[Int]): Option[Int] = {
if(a.isDefined) Some(a.get + 2) else None
}

l.flatMap(addTwo).sum
// res1: Int = 25

Ending

I personally find Option to be a very intuitive way to deal with null values. The code tends to be more readable and informative to function callers, because they are expected to handle it explicitly. As a bonus point, Option even comes with all the functional goodness about Scala collections.

Hope you will start to handle unexpected absent values in a more “expected” way using Option and get rid of those ugly null checkers lying around the codebase.