In 2009 Tony Hoare described null, his invention from the year 1965 as a billion-dolar mistake.
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
But why would he state that? Null is everywhere. We’ve all dealt with it and continue dealing with it every day. People have been doing this for decades. It surely can’t be that bad?
Well… let’s look at the following function definition:
Person GetPerson(int id);
Does it look alright?
There’s nothing apparently wrong on the first sight, but when you think about it for a while, this definition says nothing about the possible outcome of calling the function. except that it may eventually return a Person object.
var person = GetPerson(102);
Are you sure of what this line will do? Throw an exception? Return null? Blow up the Earth? Return a Person object?
If it does in fact return null in case there was no match, then what if someone forgets to handle it? The code may work for some time, it may even work for years, but eventually, we end up with:
The horrific runtime exception screen. I don’t even want to imagine what goes through the mind of a regular user when they see a window like that.
In a quality software product this should never happen.
We should be building robust software and not relying on the developers’ sixth sense to check something for null
It’s not like we have some magical mechanism that allows programmers to seamlessly move all of the null checks into the type system so they are forced to handle these situations at compile-time.
Or do we not?
The Option type (wikipedia)
an option type or maybe type is a polymorphic type that represents encapsulation of an optional value; e.g., it is used as the return type of functions which may or may not return a meaningful value when they are applied
The Option (or Maybe) type has been around for quite some time now and it enables languages like Haskell and Standard ML to completely scratch off the null value. An
Option can either have a value or be
There exists an amazing implementation for C# called Optional which you can find on github or as a NuGet package. The
Option type from this library will be used in the examples from now on.
Now, in order to clearly see the benefits, let’s rewrite our GetPerson example using the
Option<Person> GetPerson(int id);
What does this definition tell us?
Well, first, the
Option is a value type, therefore it can never be null. Second, knowing that an
Option can either have a value or not, we can instantly extract (and with, us, the compiler) that this function will either return a
Person or an empty option.
How many times have you checked MSDN or any other documentation page whether a function you’re calling will return null or throw an exception? If all definitions were as clear as our example you would never have to do that.
Okay, but the result I got is of type
Option and what I need is a Person object, how do I get the underlying value
Option type doesn’t really have a method to simply retreive the value (unless you tap into the
Unsafe library) and this is what makes it special. Instead of being given a value accessor, you get a
Match takes two parameters –
some is the function that will be executed if the option has a value, and
none is an action that will be executed when it does not.
It looks like this:
option.Match( some: x => DoSomethingWithTheValue(x), none: () => SignalThatAValueIsMissing()); // Or the generic version option.Match<T>( some: x => DoSomethingWithTheValueAndReturnT(x), none: () => SignalThatAValueIsMissingAndReturnT());
You also get
ValueOrand a bunch more goods which are documented here.
This forces you to handle both situations (when a value is present and when not) and avoid corrupt data.
And how would this look in a real-world example?
Let’s imagine that:
- We’re in the context of an ASP.NET controller.
- We want to return OK when we’ve found a person matching the provided id and Not Found when we didn’t.
Following these business requirements, our implementation would look like:
public IActionResult Get(int personId) => GetPerson(personId).Match<IActionResult>(Ok, NotFound); // Taking advantage of method groups
This is actually starting to seem great, but my team is building serious applications. Being explicit about optional values is cool and all, but what about throwing and handling exceptions gracefully?
You shall not worry. The
Option type is your friend and has you covered. It can also take an exception type.
NOTE: To be honest, this can’t really be called a Maybe/Option monad anymore. It’s an implementation of Either.
Let’s repeat our previous example, but imagine that this time we want to return a message to the user in case the provided id didn’t match any people.
Option<Person, string> GetPerson(int id); // this is getting almost as expressive as Haskell ¯\_(ツ)_/¯
What this means now is we’re either getting a person or an exception occurred, in which case the option still lacks a value, but has a cute little string for you that could perhaps explain the matter.
And how would that fit in our ASP.NET example?
public IActionResult Get(int personId) => GetPerson(personId).Match(Ok, NotFound);
It’s the same thing! Except this time the consumer would be given an error message in case of a missing match. What a bliss!
Alright, but how do I go about converting my existing objects into optionals?
If you’re familiar with FP concepts, the Option type is actually a Functor and a Monad.
mapis implemented through the
The Optional library, besides making you sing songs and dance Hula, also enables you to easily create optional values. (examples taken from the docs)
The most basic way is to use the static Option class:
var none = Option.None<int>(); var some = Option.Some(10);
For convenience, a set of extension methods are provided to make this a little less verbose:
var none = 10.None(); // Creates a None value, with 10 determining its type (int) var some = 10.Some();
Note that it is also allowed (but hardly recommended) to wrap null values in an Option instance:
string nullString = null; var someWithNull = nullString.Some();
To make it easier to filter away such null values, a specialized extension method is provided:
string nullString = null; var none = nullString.SomeNotNull(); // Returns None if original value is null
Similarly, a more general extension method is provided, allowing a specified predicate:
string str = "abc"; var none = str.SomeWhen(s => s == "cba"); // Return None if predicate is violated var none = str.NoneWhen(s => s == "abc"); // Return None if predicate is satisfied
Clearly, optional values are conceptually quite similar to nullables. Hence, a method is provided to convert a nullable into an optional value:
int? nullableWithoutValue = null; int? nullableWithValue = 2; var none = nullableWithoutValue.ToOption(); var some = nullableWithValue.ToOption();
This is barely scratching the surface
Option type is much more powerful than that.
There are numerous cool features that I have not even mentioned in this article, such as LINQ extensions, LINQ query syntax support, an absolutely amazing api for transforming and filtering values and much more. All of this is available for you on GitHub
There are better ways of handling uncertainty in your code than simply returning null or throwing undocumented exceptions. The only thing left now is to show off your newly aquired knowledge by writing some robust, exception-free code.