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

07.30.2010
| 7306 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?

 

From http://beust.com/weblog/2010/07/28/why-scalas-option-and-haskells-maybe-types-wont-save-you-from-null

Published at DZone with permission of Cedric Beust, author and DZone MVB.

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

Comments

Sean Parsons replied on Fri, 2010/07/30 - 3:48am

C# provides a similar mechanism for value types as Fantom, by using the "?" modifier to signal nullability.

Lukasz Zwierko replied on Fri, 2010/07/30 - 4:10am

I think you're missing a point here, I'm not much into scala but looking at the example from the post you've given link to (http://blog.danielwellman.com/2008/03/using-scalas-op.html) I see what authors really meant.

 

import scala.collection.mutable.HashMap
val map = new HashMap[String, String]
map.put( "Hi", "Dan" )
map.put( "Hello", null )

Here we've set up the same Map as we did in Java, but we imported Scala's mutable version of a HashMap. Now let's make the same queries into the map:

scala> map.get( "Hi" )
res1: Option[String] = Some(Dan)

Here the Map has the requested key, and returns a Some[String] which has contains the value of "Dan".

scala> map.get( "Hello" )
res2: Option[String] = Some(null)

In this case, the key does exist, so it returns a Some class containing the value null.

scala> map.get( "Gutentag" )
res3: Option[String] = None

 

See the difference between Some(null) and None?

Some(null) == object is defined and references null

None == object is not defined/ uninitialized

 I think that's the whole point. It doesn't savne you fron checking null pointeers, but it does enable you to distinguish between unnintialized value and valu initialized with null (if you'r into thiskind of programming).

Héctor Hugo Bar... replied on Fri, 2010/07/30 - 6:39am

I think main point of blog article is in last paragraph.

Ultimately Scala's Option class requires that the programmer more explicitly handle conditions where data might be missing. In fact, the problem now gets handled by the compile-time type system -- if the user forgets to handle the case, Scala will generate a compile error.

 Scala's Option change runtime exception such as NullPointerException with a compile-time error, so developer cannot forget to handle the case (even if you want to).

 

Ricky Clarkson replied on Fri, 2010/07/30 - 6:45am

"You avoid a NullPointerException by… testing against null, except that it's called None. What have we gained, exactly?"

We have gained because we have done this deliberately. The insidious problem of null is that it is generally impossible to know whether a reference can be null, whereas we always know an Option can be None. x.foo() doesn't look any different whether or not it can throw a NullPointerException.

"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?"

There's nothing to stop you from throwing an exception at this point. Option has .get, which gives you the value or throws an exception. Again, the distinction is that dereferencing an Option looks different to dereferencing a nullable variable.

I'm pretty sure we've discussed this in the past; I'm surprised you still don't understand this, Cedric.

Josh Berry replied on Fri, 2010/07/30 - 10:18am

I believe you missed out on probably one of the nicer figures of options, the use of map/flatMap.  Combined with for comprehensions, you can push many null pointer checks into a single spot.

 

Consider:

scala> var x = Option(3)
x: Option[Int] = Some(3)

scala> var y = Option(4)
y: Option[Int] = Some(4)

scala> var z = Option(1)
z: Option[Int] = Some(1)

scala> for (a <- x; b <- y; c <- z) yield (a + b + c)
res0: Option[Int] = Some(8)

scala> z = None
z: Option[Int] = None

scala> for (a <- x; b <- y; c <- z) yield (a + b + c)
res1: Option[Int] = None

 

David Parks replied on Sat, 2010/07/31 - 4:05pm

I'd agree with Josh Berry and take it a step further -- the shining benefit is when used with monadic computation.  Josh's examples are of that sort.  As are your Fantom examples (when you note that Str? is basically a less verbose way of writing Option[String]).  Either is another great example from Scala -- it largely replaces the explicit boilerplate required for exception handling.

I'd read just about everything you were talking about and I didn't get the point when I did, either.  Its probably the case that it clicks differently for everyone but I can tell you that the moment I "got it" was when I read the first few pages of the Haskell All About Monads Tutorial.  Now, I don't know Haskell... but the core points of the language are pretty simple and anyone who knows Scala can easily comprehend the first bit of this tutorial.  It really communicates why you would care about monads, and when and how you write and use them.


 

 

 

Eric Giese replied on Mon, 2010/08/02 - 7:30am

There are even more benefits of Option / Maybe:

1. If you use Option for all optional values, the callers do not need to check for null anywhere. Null-Checks then become a hint on bad design, as do instanceof-checks in JDK 1.5+ code. Also, if a NoPE appears anywhere, its easy to blame (and fix) the provider of the value, instead of discussing wether the caller must check for null.

2. Optional values require additional work on the callers side, but thats intentional:

  • You will use optional them rarely, making the code easier to understand.
  • It prevents the dreaded "return null" paradigm which most of the coders I know use as a last resort:
    In this case, the implementor now needs to reason wether this scenario is recoverable (-> use option) or a dead end (-> throw an IllegalStateException or something like this).
  • Callers cannot forget to check for null and see the "optionality" in the signature. Also, they know that with "normal" values, they do NOT need to check for null defensively.
  • Changing between "normal" and optional representation enforces a refactoring. This helps to reduce maintenance costs: All consumers of the function will go "red" immediatly, instead of a nasty NoPE which waits in the background.

Also, unboxing isn't that much of an effort, if you are not interested for the "none" value:
for ( v <- value){
v.something();
}
Thats not much more work than a null check.

Ben James replied on Tue, 2011/04/05 - 11:08am

The worst part about this example is that it forces me to deal with the null case right here.

You've chosen a poor example. Matching on an Option is silly, because there are methods that can deal with the None case for you.

Here's how a Scala equivalent of your Fantom example might look:

    val email: Option[String] = users.flatMap(_.get("ben")).map(_.email)

Or, in for-comprehension syntax:

    val email: Option[String] = for (userList <- users; user <- userList.get("ben")) yield user.email

James Kear replied on Tue, 2011/09/06 - 3:33pm

But both Scala and Clojure must live with the design decisions on the JVM which were taken before them, and with interoperating with a wealth of existing libraries. hire a programmers

Rehman Khan replied on Sat, 2012/02/25 - 3:38am

The point about Maybe and Option is not that they save you from ever having null, but that you only get null when you want it. If it is semantically impossible for a value to be null, the type checker can enforce it.

This is a massive win over getting that horrible null pointer exception and trying to work out exactly how the null crept into some variable that was never supposed to be able to hold it.

Comment viewing options

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