I am a software engineer at Google on the Android project and the creator of the Java testing framework TestNG. When I'm not updating this weblog with various software-related posts or speaking at conferences, I am busy snowboarding, playing squash, tennis, golf or volleyball or scuba diving. Cedric is a DZone MVB and is not an employee of DZone and has posted 90 posts at DZone. You can read more from them at their website. View Full User Profile

Why Scala’s “Option” and Haskell’s “Maybe” types won’t save you from null

08.05.2010
| 5727 views |
  • submit to reddit

The more I think about it, the less I understand the point in Scala’s Option class (which originated in Haskell under the name Maybe).

If you read the voluminous material that describes the concepts behind the Option class, there are two main benefits:

  • It saves you from NullPointerException
  • It allows you to tell whether null means “no object” or “an object whose value is null”

I claim that Option solves none of these problems. Here is why.

Here is a typical blog post showing the wonders of Option (I’m not singling out this particular person, you will find many posts making similar claims).

The examples in this post show that with the Option class, you can now have hash tables that contain null values and never be confused when a get() returns null. Fine, but in practice, I find that hash tables allowing null values are rare to the point where this limitation has never bothered me in fifteen years of Java.

The claim that Option eliminates NullPointerException is more outrageous and completely bogus. Here is how you avoid a null pointer exception with the Option class (from the blog):

val result = map.get( "Hello" )

result match {
case None => print "No key with that name!"
case Some(x) => print "Found value" + x
}

See what’s going on here? You avoid a NullPointerException by… testing against null, except that it's called None. What have we gained, exactly?

The worst part about this example is that it forces me to deal with the null case right here. Sometimes, that's what I want to do but what if such an error is a programming error (e.g. an assertion error) that should simply never happen? In this case, I just want to assume that I will never receive null and I don't want to waste time testing for this case: my application should simply blow up if I get a null at this point.

And you know what construct does exactly this? NullPointerException! Try to reference a null pointer and that exception will be thrown. It will make its way up the stack frames since you probably never catch it anywhere (nor should you) and it will show you a clear stack trace telling you exactly what happened and where.

In other words, it seems to me that the Option class is bringing us back into the stone age of using return values to signal errors. I can't imagine this being a progress (and I'm equally irritated at the Go language for making the same mistake).

When it comes to alleviating the problems caused by null pointer exceptions, the only approach I've seen recently that demonstrates a technical improvement is Fantom (Groovy also supports a similar approach), which attacks the problem from two different angles, static and runtime:

Static: In Fantom, the fact that a variable can be null or not is captured by a question mark appended to its type:
Str   // never stores null
Str? // might store null

This allows the compiler to reason about the nullability of your code.

Runtime: The second aspect is solved by Fantom's "safe invoke" operator, ?.. This operator allows you to dereference a null pointer without receiving a null pointer exception:
// hard way
Str? email := null
if (userList != null)
{
user := userList.findUser("bob")
if (user != null) email = user.email
}

// easy way
email := userList?.findUser("bob")?.email

Note how this second example is semantically equivalent to the first one but with a lot of needless boiler plate removed.

So, can someone explain to me how Option addresses the null pointer problem better than Fantom's approach?

References
Published at DZone with permission of Cedric Beust, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Philip Andrew replied on Thu, 2010/08/05 - 9:21pm

Look at the openOr method. result.openOr("nothing")

Daniel Pratt replied on Fri, 2010/08/06 - 8:46am

I don't really have any experience with Scala, but I know a bit about Haskell. Why is the Maybe data type in Haskell a good thing? Probably most importantly, most of the time values are not of that type, meaning that you don't have to worry about values being Null/Nothing. Contrast that with the JVM, where anything (variable, parameter, method return, etc.) of a class type may be 'null'. Considering this fact, it's easy to see why 'NullPointerException' is such a headache. There's just too many places to check!

In the case that you do have to deal with Maybe values in Haskell, you have two practical options:

  1. Use pattern matching (as per your example) to decide how to handle 'Nothing' values.
  2. Make the type of the containing expression/function a Maybe type. In other words, defer the decision of how to handle the 'Nothing' case to a higher level.

Considering the fact that Haskell functions never directly perform side-effects (such as printing values to a console window), but rather return 'action' values describing the side-effect(s) to be performed, this approach is probably somewhat more practical than it would be in Scala.

By the way, the Maybe Monad and 'do' notation in Haskell yield very similar semantics to Fantom's 'safe invoke' operator.

Gabriel Claramunt replied on Mon, 2011/08/15 - 12:48pm

Yes, Option can save you from null pointer exceptions https://gist.github.com/1147238

King Sam replied on Fri, 2012/02/24 - 10:57am

As for your solution A, that is exactly what I am talking about. In order to use Option safely, you have to hide the use of Option behind a null check (in your case, it’s in the Option.apply). The compiler won’t do it for you just by using for-comprehensions or flatMap; var Option[Foo] = _ won’t default to None; someone can still pass a null container or a container holding null as Some(null). That’s something I have seen people get confused about, and these responses that you should *never* have to check for null seemed likely to continue that confusion.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.