Handling missing values in Scala: Options
Whenever we want to use a value that is not readily available yet – for example, when we call a function to compute or fetch something over the network – we can not be sure if things go as were planned. What if the database connection is down? What if the user typed in an invalid string?
In the Java world, and in many other programming languages, the programmers could throw an exception (or just pass further the ‘classic’ NullPointerException) in these cases to signal that the requested value cold not be served.
But oftentimes, using exceptions for such cases is an overkill, and only adds noise to the code. It can even happen, that it is completely ‘normal’ that a function can not return a proper value for some reason.
Returning something or nothing
Scala has a built-in type to help us in the situation when we are not sure that we can return a value: the Option[T] type. The T parameter means that it takes a type inside it as well, and it will be type-safe to use.
You can think about an Option as a container that either holds a value of type T, or not. If the Option is empty, it is called None, and if it has a value inside, then it is an object of the class Some[T].
How to use Options
Let’s see a couple of examples of how we can use Options in Scala to represent optional values.
Returning an Option from our function
The following “getUserById()” function returns an Option (about Maps, see the previous post):
def getUserMap: Map[Int, String] = {
val userMap: Map[Int, String] = Map(
824 -> "Jane",
2723 -> "Kate",
535 -> "Zoe",
8260 -> "Jonathan")
userMap
}
def getUserById(id: Int): Option[String] = {
val users = getUserMap
// Note: we could have just used
// Map's buil-in get() method that returns an Option:
// users.get(id)
if (users.contains(id)) {
Some(users(id))
} else {
None
}
}
println("535: " + getUserById(535))
println("550: " + getUserById(550))
The last two lines will print:
535: Some(Zoe)
538: None
So, the String value “Zoe” itself is wrapped in “Some()” in the first case, and we simply got a “None” result in the second case, when we used a non-existing id.
Dealing with an Option
What can we do if we receive an Option from somewhere?
Scala offers many possibilities to handle Options, here I present the two simplest ones.
Unwrapping an Option and defining a default value
If you simply want to unwrap the inner value from a Some, and default to something (of the same type as the wrapped value would be) in case of a None, just use the “getOrElse()” function of Option:
val maybeString1: Option[String] = None
println(maybeString1.getOrElse("default value"))
val maybeString2: Option[String] = Some("Hello")
println(maybeString2.getOrElse("default value"))
This example will print “default value” at first, and then “Hello” to the screen.
Using pattern matching
Usually it seems to be an overkill to use pattern matching to handle Options, but at least we have already covered it here. So let’s see how to do this:
val result: Option[Int] = None
val toReturn: Int = result match {
case Some(num) => num
case None => 0
}
println(toReturn)
Other possibilities
For more smart ideas on dealing with Options, see Daniel Westheide’s blog post.
Summary and other sources
By using Options we, as developers of a certain functionality, can clearly signal that our function might return without a proper result. Scala also helps (and requires) the caller of such function to deal with the situation: for example, by defining a default value.
Daniel’s post on Options goes into more details, it’s really worth reading.
Some runnable code examples are available here.