Tony Hoare called them his billion dollar mistake, but we all know them, null references.
We can probably all agree that we’ve all had our fair share of annoyances with these infamous little things,
so today we’re going to explore how different programming languages choose to handle (or not handle) null
(or nil
) values.
This has been a topic for some time now and I always wanted to have some kind of comparison between different solutions,
with their respective advantages and disadvantages.
First let’s look at an example how NOT to handle null
in Java 7.
You’ve probably seen something like this before and probably hoped never having to do it again.
The first solution I want to look at is the “Safe navigation operator” ?.
in Groovy, which I believe was the first to implement it.
With the Safe navigation operator we can chain the navigation without risking a NullPointerException.
The above can be written in Groovy like this:
Last year in 2015, Ruby and C# added this feature as well.
C# uses the same syntax as Groovy:
Ruby went with &.
to avoid confusion, since Ruby allows questions marks in their method names to signal a returned boolean value:
This is a great addition to the respective languages, however it does have one flaw. These null-checks are never enforced, so you can forget about them quite easily and introduce subtle bugs into your code base.
Other functional languages, such as Scala, F# and Haskell, take a different approach. They strive to completely eliminate the null value from their Type System.
Instead Scala and F# provide an Option
type, while Haskell provides a very similar Maybe
type.
A value of type Option
or Maybe
can either hold a value of a given type or nothing.
This way you have to do an explicit check everytime you want to access a value that might not be there. It’s impossible for you to “forget”, because the type system enforces it.
Java adopted this kind of system in Java 8, which includes other functional programming features. We’ll see why that’s important later. First let’s look at an example in F#.
And here’s the same thing in Java 8:
map
is a higher order function that transforms the value inside the Option
if it exists and does nothing if it’s absent.
flatmap
does the same thing, however it flattens the result, so you don’t get nested Options like this: Optional<Optional<Optional<String>>>
The same thing can also be applied in Scala:
However Scala also has a special syntax using for-comprehensions, which are much more readable if you ask me. Here’s how that would look:
This is a very good way to handle optional values and forching the user to handle null explicitly is a big step forward if you ask me. However one might argue that the Groovy, C# and Ruby solutions are much shorter and more concise. That’s why in the last few years languages have tried to combine the two concepts. We’re going to have a look at how Swift and Kotlin thrive to combine the two, for maximum readability.
First let’s look at an example in Swift:
So this is the exact same way Java, Scala and F# do it, but in Swift you can convert it to this:
So Swift invokes some syntactic sugar similar to Scala to improve readability.
In the same sense you can write Optional<String>
as String?
.
Kotlin does this the same way:
So in these examples firm
would either be a valid string, or null
in kotlin and nil
in Swift.
In both languages, if you don’t explicitly make your variables optional by adding a ?
after its type, it has to be initialized.
This makes dealing with null
values much more reasonable.
So that’s the rundown on the different strategies of dealing with null
.
I personally really like the syntactic sugar Kotlin, Swift and Scala provide and the absence of a real null
in Scala, F# and other functional programming are even better.
What do you guys think about this? What’s your favorite solution? Let me know!
References:
Groovy: http://www.groovy-lang.org/operators.html#_safe_navigation_operator
Kotlin: https://kotlinlang.org/docs/reference/null-safety.html
Java 8: http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html
Ruby 2.3: https://www.ruby-lang.org/en/news/2015/12/25/ruby-2-3-0-released/
C# 6: https://msdn.microsoft.com/en-us/library/dn986595.aspx