After reading a post by Cédric Beust that linked to an earlier article I wrote about Scala's Option class, I’ve decided to write about what I’ve learned about Option in the past two years. Cédric brought up some good points about how that article’s pattern matching example using maps doesn’t feel drastically different than checking for nulls in Java.
The code examples in the previous blog post demonstrate using Option with pattern matching. I found this to be an easy way to learn about pattern matching, however after reading and writing more Scala code, I’ve learned that there are more efficient ways to use Option:
1. Use the getOrElse() function for simple this-or-that cases. The Map getOrElse(key, default) function will return value contained in the Some() container or the specified default otherwise. Using the map from the example above, you could write something like this:
scala> val map = Map("Hi" -> "Dan", "Hello" -> null)
map: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map((Hi,Dan), (Hello,null))
scala> map.getOrElse("Hi", "No key with that name!")
res1: java.lang.String = Dan
scala> val result = map.getOrElse("Gutentag", "No key with that name!")
res2: java.lang.String = No key with that name!
In our example, since “Gutentag” doesn’t exist in the map, result will be the “No key with that name!” message. (Technically this is not the same as the original pattern matching example, as this code doesn’t append the “Found value” string before a found key, but for this example the important part is “or else” functionality.) This code feels shorter and more concise to me.
Note that the Option class also has a getOrElse():
Some(“foo”).getOrElse(“Nothing!”) // returns “foo”
2. Use Options and for comprehensions to simplify a series of calls which may return a result or no value. Daniel Spiewak’s comment on Cédric’s blog post provides a quick example of this; I’ll post the code here, but please do read his comment for a detailed explanation (and some more thoughts on Option in Scala):
val value = for {
row <- database fetchRowById 42
key <- row get "port_key"
value <- myMap get key
} yield value
val result = value getOrElse defaultValue
This code will set result to either a Some value if there’s a database record with the given row id, that row has a “port_key” value, and myMap holds an entry with the referenced key or None if any of those lookups fail. The getOrElse call will either return the value stored in myMap or else some default value.
The concise for comprehension syntax works well when you can accept a default value in the event of failure (an “all or nothing” design approach), but if you need to handle an error at each individual step, you’ll need to do something else. Lift’s Box class is similar to Option but has hooks for error handling.
3. Option is about programmer intent. JSR-305 has proposed @NonNull and @Null annotations for Java to allow programmers to indicate that a method may or may not return null. Scala’s Option class provides a similar indication to programmers; this function may return a value (Some) or it may not (None.) Java JSR-305-aware compilers can provide warnings at compile-time if a null value might be dereferenced. Scala’s compiler can also provide a warning if you use pattern matching against an Option class and don’t check for both Some and None cases.
However, Scala’s Option class gives you one thing Java’s null value does not – behavior. Option defines methods that you may safely call (like getOrElse, map, etc.) whether you have a result (Some) or not (None.) Using these methods along with for comprehensions seems to yield fairly concise code.
Comments
You can follow this conversation by subscribing to the comment feed for this post.