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.
#7 by Harshad on October 21, 2010 - 10:44 pm
Quote
I would summarize the arguments defending Option as:
Option is the same as List, albeit restricted to one element. You could replace every instance of Option with List, and None with Nil. And that is where it’s beauty/efficiency lies.
It has got nothing specifically to do with “null”. Except that you can avoid the common and questionable practice of using “null” to flag error conditions, and use Option/List instead.
#8 by Anonymous on October 30, 2010 - 5:27 am
Quote
You want something which makes the program blow up if you have a None?
Oh look!
val x = (mapping get “x”) get
#9 by Blaine on November 10, 2010 - 9:05 am
Quote
I believe the idea with most languages that throw away null is that if you want something to be optional, you must explicitly say so.
In languages where the possibility of null is always there, you have added complexity to handle. If you slim down your possible states by default, there are less conditions that you need to test.
Here’s a really good explanation and reasoning as to why it’s a good thing:
http://stackoverflow.com/questions/3989264/best-explanation-for-languages-without-null/3990754#3990754
#10 by ianam on December 11, 2010 - 4:39 am
Quote
This article is dimwitted; Fantom’s Str? is a weaker version of Scala’s Option[String].
#11 by Jeffrey Bolden on December 14, 2010 - 8:37 am
Quote
This is an old post but I’m surprised no one really dealt with the issue. The point of Option with Nulls is not on a single function. There it doesn’t do much. The real point is:
1) It allows you to use functions that normally would error on Null freely.
so if you normally you would have code like
if x is not NULL then y = f(x)
you can replace with with an fmap y’ = fmap (f) (x)
and y’ will be NULL if x is null and otherwise will be f(x) (modulo) the option
2) Even better you can chain operations together and not worry about NULLs. So for example if f,g,h,i can all throw a NULL or work and you would like
z = i . h . g . f (x) without option would end doing something like:
if x is not NULL then let f’ = f(x).
if f(x) is not NULL then let g’ = g(f’).
etc…
but… with Option you can just find these guys in a chain:
z = i =<< h =<< g =<< f =< Option Checkmating_Sequence
#12 by Jeffrey Bolden on December 14, 2010 - 9:06 am
Quote
Well this ate part of my comment because of the puntuation.
g’ = g(f(x))
and
z = i =<< h =<< g =<< f =< Option Checkmating_Sequence
#13 by Jeffrey Bolden on December 14, 2010 - 9:07 am
Quote
z = i =<< h =<< g =<< f =<< x
and forget the chess board that just keeps getting eaten.
#14 by Frank on March 22, 2011 - 1:50 am
Quote
I appreciate your arguments.
In Scala there is one nice notation possible
for (result <- map.get( "Hello" ))
println("Found Value: " + result)
The body of the for expression is only executed if the option is nonempty. This is possible because Option[T] implements the methods foreach, map, flatmap and filter. It's a so called monad.
I would prefer a notation like
if (result <- map.get( "Hello" ))
println("Found Value: " + result)
because it is not a real loop. But I don't think it's possible.
#15 by ertes on April 26, 2011 - 4:30 am
Quote
I don’t know much about Scala, but in Haskell the Maybe type does much more than just allowing static checking for missing values. First of all, the concept of a “null pointer” does not make much sense in Haskell, because there is no difference between value and reference. In Haskell, every type denotes a set of values. If there is a “no value” value in the set, then it needs to be denoted specifically. One method would be this:
data Int = NoInt | 0 | 1 | 2 | …
But more flexible is the Maybe type constructor, which can be applied to any type to add a “missing value value” to an existing set without requiring the underlying type and the operations working with it to know anything about it.
This is probably the main difference between a nullable value in C# and Maybe in Haskell. Maybe does not touch the underlying type at all, and functions using it do not need to be aware of the wrapping Maybe. Why do will still not need explicit tests? Because Maybe is a functor and even a monad. Given the function
mSqrt :: Integer → Maybe Integer
the following code is perfectly fine:
mFourthRoot :: Integer → Maybe Integer
mFourthRoot = mSqrt >=> mSqrt
In the Maybe monad the (>=>) combinator takes the role of Fantom’s question mark. It is monadic function composition. In other words, you get the functionality of Fantom without needing the language to be aware of nullability. That keeps the core language compact and simple. Also monads are a much more general concept. Given a square root function in the list monad, which can return multiple results, you can compose with the same combinator above. The code is exactly the same and can even be generalized to all applicative functors with alternation (Maybe and lists are such):
mSqrt :: (Alternative f, Integral i) ⇒ i → f i
mSqrt = …
mFourthRoot :: (Alternative f, Integral i) ⇒ i → f i
mFourthRoot = mSqrt >=> mSqrt
I think, Scala tried to duplicate this concept, but with much less elegance and simplicity.
#16 by Eric Kow on May 26, 2011 - 5:38 am
Quote
I don’t think anybody has tried driving straight to this point yet: it’s not about the Option/Maybe themselves but the non-nullable payload they contain. The point isn’t that you can write code for Option, but that the majority of your code will be for Foo, which means no NPEs. Option just lets you loosen this for the rarer cases where you do want a null value.
While this repeats a lot of what’s been said above, I hope this more direct (?) phrasing is helpful to somebody. I’d be curious to see a follow-up to this post, now that some time and rebuttals have passed.
#17 by Gabriel C. on August 15, 2011 - 8:59 am
Quote
Yes, Option can save you from null pointer exceptions https://gist.github.com/1147238
#18 by Brien Colwell on September 1, 2011 - 6:53 pm
Quote
The example here isn’t constructive, since no matter how it’s written, Option or not, it will never throw an NPE! For example, println(“Found this: ” + map.get(“Hello”)) . If you were actually doing something with the return value, then you’d see Option makes sense in some cases.
For example, do something with the value in the map:
for (item <- map.get("Hello")) { item.doSomething() }
Or transform the result
val transformedItem = map.get("Hello") map { transform(_) }
These handle the null case much more elegantly than
val item = map.get("Hello")
if (null != item) { item.doSomething() }
val transformedItem = null == item ? null : transform(item)
Why? Because Some and None are handled identically, whereas null must always be handled separately.
There are problems with Options, but this post doesn't show them.
#19 by Nikita Volkov on January 14, 2012 - 5:50 am
Quote
The point of Scala’s approach in difference to Fantom and Kotlin is that it doesn’t make a special case for nulls – instead it treats null as a rudimentary part of Java’s legacy and offers Option as a natural replacement for it (as you might know Scala was hugely inspired by Haskell and Haskell has no nulls). So Any? is a Fantom alternative for Option, but what’s the alternative for Either, and what are the alternatives for any other powerful type constructs you can make – Fantom has none. Before I studied Haskell I was a bit confused by Scala’s Option concept and staying Java-minded leaned towards the Any? approach of Fantom and Kotlin. Now I can state that those guys made a huge mistake by sticking to null, so do you in this blog post.
#20 by Cristian on January 26, 2012 - 12:53 am
Quote
Felt the same in the beginning but if you are persistent in trying to learn “the functional way” you’ll understand eventually. Scala doesn’t make it easy because it allows you to write tons of bad code.
And yes, as was stated before, using match or get on an Option/Box is just bad functional code. Just assume those methods/constructs never exist. You’ll be forced to use getOrElse or go with a map/flatMap/foreach. Then you’ll really see that you can’t get NullPointerException and will lead you to write code that you’ll love.
As David Pollak explained to me what functional programming tries to promote is that programming should not be flow of control, but should be expression of relationships. Put in research terms, Option/Box and other functional constructs significantly reduce Cyclomatic Complexity.
Going to your code, if all you’re trying to do is print that value, the much simpler and null safe code is:
map.foreach( x => print “Found value” + x)
This won’t print anything if the Option is None. If you want to still print something you can either:
print “Found” + map.getOrElse(“Nothing”)
or
print map.flatMap(x=>”Found value”+x).getOrElse(“Found nothing”)
All these options are more concise than your code and doesn’t allow null to creep in. As I said, just ignore get on options.