Angle Brackets and Curly Braces

Percolating Nones in Scala

by Bill Venners

September 12, 2009



Summary

Scala has an Option type that provides a type-safe alternative to using null to represent optional values. In this blog post, I show how to replace nested conditionals involving Option with a for-expression.


Scala's Option type has only two possible subtypes, Some[T] and None . If an Option is a Some , the optional value does indeed exist. If it is a None , however, the value does not exist. In other words, None indicates in Scala what null often indicates in Java.

Idiomatic Scala APIs use Option to represent optional values. For example, here's a find method that looks for a specified character, c , in a specified string, s :

def find(c: Char, s: String): Option[Int] = s.indexOf(c) match { case -1 => None case c => Some(c) }

If the string contains the character, find returns the index of the first occurence of the character wrapped in a Some . Otherwise, it returns None to indicate the character does not exist in the string.

Here's another example. The superChar method returns the character at the specified index, i , of the string, "supercalifragilisticexpialidotious", wrapped in a Some , or None if i is greater than or equal to the length of "supercalifragilisticexpialidotious":

def superChar(i: Int): Option[Char] = { val song = "supercalifragilisticexpialidotious" if (i < song.length) Some(song(i)) else None }

In the unlikely event that you want to determine the character in supercalifragilisticexpialidotious at the same index as the first occurrence of a particular character in a particular string, you could first call find . If it's result is a Some , you have the index of that character in the string. You can then pass that index to superChar . If it returns a Some , then you have the character in supercalifragilisticexpialidotious that sits at the same index as your chosen character in your chosen string. Now imagine further you want to find the index of the first occurrence of this result character in your original string. You could invoke find again, this time passing in the result character and the original string.

Now three things could go wrong here:

Character c might not be in string s. Character c is in string s, but at an index greater than or equal to the number of characters in supercalifragilisticexpialidotious. If the previous two problems didn't materialize, the result character still might not be in string s.

The traditional Java-style approach to deal with this situation with a nested if-else construct, as is done in the following percolate1 method. If all goes well, it returns the desired index, wrapped in a Some . But if any of the three potential problems happen, it returns a None :

def percolate1(c: Char, s: String) = { // (Not idiomatic Scala) val index = find(c, s) if (index.isDefined) { // isDefined is true for Some, false for None val resultChar = superChar(index.get) // get grabs the value out of the Some if (resultChar.isDefined) { find(resultChar.get, s) } else { None } } else { None } }

A more idiomatic way to accomplish this in Scala is with a nested pattern match. That would look like this:

def percolate2(c: Char, s: String) = { find(c, s) match { case Some(index) => superChar(index) match { case Some(resultChar) => find(resultChar, s) case None => None } case None => None } }

While this is a bit more concise and leaves less room for error than the nested if-else construct, Scala has an even better way to accomplish this. You can use a for-expression, like this:

def percolate3(c: Char, s: String) = for { index Using any of these approaches, a None will percolate out no matter where the failure occurs. Here are some examples in the Scala interpreter. Given c and "cats" , percolate3 will return Some(3) because c is at index 0 in cats , index 0 in supercalifragilisticexpialidotious contains s , and s in "cats" appears at index 3: scala> percolate3('c', "cats") res0: Option[Int] = Some(3) The previous example made it all the way through. If any of the three potential problems occurs, a None will occur at that step and percolate out all the way to the result. Here the first call to find results in None , so None percolates out as the result of percolate3 : scala> percolate3('d', "cats") res1: Option[Int] = None Here the initial call to find returns Some(68) , but because 68 is greater than the length of supercalifragilisticexpialidotious, the subsequent call to superChar results in a None , which percolates out: scala> percolate3('c', "--------------------------------------------------------------------cats") res2: Option[Int] = None And here the first call to find results in Some(2) , and superChar results in Some(u) , but because "cats" doesn't contain a u , the second call to find results in a None , which is returned: scala> percolate3('a', "cats") res2: Option[Int] = None Using a for expression for this can seem non-intuitive because we're used to doing this kind of thing to iterate over collections. One way to think of an Option is as a collection that can contain either zero or one value--like a special kind of List that can either be an empty List or a List with only one value in it. The nice thing is you can compose up to as many operations like this as you need and be confident None s that happen at any step along the way will simply percolate through to the end. Talk Back! Have an opinion? Readers have already posted 4 comments about this weblog entry. Why not add yours? RSS Feed If you'd like to be notified whenever Bill Venners adds a new entry to his weblog, subscribe to his RSS feed. About the Blogger

Bill Venners is president of Artima, Inc., publisher of Artima Developer (www.artima.com). He is author of the book, Inside the Java Virtual Machine, a programmer-oriented survey of the Java platform's architecture and internals. His popular columns in JavaWorld magazine covered Java internals, object-oriented design, and Jini. Active in the Jini Community since its inception, Bill led the Jini Community's ServiceUI project, whose ServiceUI API became the de facto standard way to associate user interfaces to Jini services. Bill is also the lead developer and designer of ScalaTest, an open source testing tool for Scala and Java developers, and coauthor with Martin Odersky and Lex Spoon of the book, Programming in Scala.

This weblog entry is Copyright © 2009 Bill Venners. All rights reserved.