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")?.emailNote 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?
#1 by Cody Koeninger on August 16, 2010 - 6:03 am
Quote
Regarding solution B, thank you for a more correct definition of that syntax sugar in scala, the previous qmark definition above wasn’t checking for null.
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.
#2 by NoBody on August 16, 2010 - 6:50 am
Quote
I see your point.
this solution is sadly not that better:
def test(u: Option[UserList]) = {
println(Option(u) flatMap (_.flatMap (_.findUser(“bob”)) flatMap (_.email)) getOrElse(“default”))
}
I agree that scala should doing here better.
#3 by Tony Morris on August 17, 2010 - 9:01 pm
Quote
Cody, I give up.
#4 by Tim Band on August 19, 2010 - 6:29 am
Quote
Seriously?
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.
#5 by harryh on August 27, 2010 - 12:36 pm
Quote
This is bad scala code:
val result = map.get( “Hello” )
result match {
case None => print “No key with that name!”
case Some(x) => print “Found value” + x
}
You should do this:
print map.get(“Hello”).map(“Found value “+_).getOr(“No key with that name!”)
Option saves you from null because whenever you access it it (should) force you to remember “Hey, there might not be an actual thing here, how should I deal with it if we don’t have one.”
-harryh
#6 by James Iry on August 27, 2010 - 2:26 pm
Quote
I’ve posted a full rebuttal at http://james-iry.blogspot.com/2010/08/why-scalas-and-haskells-types-will-save.html . I think it covers most of the main points made in the comments here.