Tony Hoare calls null references his billion dollar mistake. Using
null values (
nil, etc) makes code harder to maintain and to understand.
But what can we do about it? To start let's review the meaning of
null values ...
Modeling optional results
Finding operations are very common. Given a
Customer class, a
Find method could look like this:
Now what happens when the customer can not be found? Possible options are:
Throw an exception: Why though? Where is the exceptional case? Trying to find a
Customerhas the possible scenario of not beign found.
nullto mean nothing was found. But are we positive we are getting a
nullfor that reason and not other? And what can we do with the
Customernull value. That could work to show some of the customer's data, if we expect strings or something similar. But for most cases this won't be enough.
In C# you can use
Int32.tryParse to do the job. The former throws an
Exception when the string can not be parsed into an int and the later returns a
Bool indicating if the operation succeeded using an
out parameter for the value.
The first approach is not that intuitive. I want to get a result, not to catch an exception.
The second one with the boolean result seems to go in the right direction but having an
out parameter complicates things, and makes it hard to understand and hard to pass to another function, etc.
F# has a very simple way to deal with this by creating a Discriminated Union with only two values:
Now finding customers has a very clear interface:
Parse clearly can succeed giving the parsed result or fail, therefore the result is
NOTE: out parameters are converted tuples in F#.
As a convention is common to call functions
trySomeAction when the action can fail and return an optional value.
Working with optional values
Modeling optional values is a great start. Using an optional value makes very clear the fact that the caller has the responsibility to handle the possiblity of having
None as a result.
Having clear meaning improves clarity, intent, error handling, and there is no
null that can cause problems.
However, in terms of handling the result, are we that much better than before?
Of course we could check always for
None and handle the result, but where is the
fun in that?
The key to create a great abstraction is usage. After seeing the same bit of code used again and again we could be confident that abstracting the behaviour is going to be really useful.
To avoid doing a
Option types luckily we have a series of helper functions that address most common scenarios.
Many of these functions live in the FSharpx library.
To obtain the value from an
Option we can use
function? Here is an explanation about pattern maching function)
get throws an exception when there is nothing to get.
Throwing exception (and catching it) is ok, but makes hard to compose the result or transform it.
Instead why not use a default value when there is
None? (Taken from FSharpx):
Knowing that it can fail, and having a default value help us write code that can use a default value and keep going:
Another common scenario is to apply a function when we get a result or just do nothing otherwise:
For example, the following code will only print the message when
And the implementation:
iter is useful to execute a function when there is
Some value returned, but is common to use the value somehow and transform it into something else.
For that we can use the
This is a bit different. Not only the function passed as parameter is applied after unboxing the value, but the result is boxed back into an
Option. Once an
Option always an
Imagine a railway and a train that once hits the value
None switches to a
None railway and bypasses any operation that comes after. Thus the shortcircuit.
Another common case, very similar to
map, is to use a function that transforms the boxed value and returns an
For example the parameter that represents the customer id is a string, and we need to parse it into an int. Getting the parameter can return a
None and the parse function could return a
None as well.
Multiple optional values
So far so good with one parameter. But what happens with more than one parameter? The goal is to shortcircuit and if one of the parameters is
None then abort and just return
One option is to use FSharpx and the
MaybeBuilder. I'm not going to discuss the details of how builders work but I will show you the practical usage to illustrate the point.
In this scenario we have a happy path:
- All parameters are present, then the result is
Successwith the output of
And four unhappy paths:
countis not present, then the
maybebuilder does shortcircuit to
cityis not present, then the
maybebuilder does shortcircuit to
countryis not present, then the
maybebuilder does shortcircuit to
countis present, but can not be parsed then ... shortcircuit ... and
let! is doing the unboxing from
Option to the actual type, and when any of the expressions has the value
None then the builder does the shortcircuit and returns
None as result.
Another way to write the same concept (sometimes a bit more clear) is to use the Applicative style by using operators that represent the operations that we already are using that also apply shortcircuit when possible.
For the functions we have used in the
Option type the operators are:
(Find all the definitions here).
To use them let's try to read the parameters from the request.
count needs to be parsed as well, so we can use
tryParse that returns an
Option. What can we use when we need to apply a function that returns also an
Bind of course, or
All the parameters are parsed into
findCustomers can be invoked.
To apply a function over an
Option we can use the operator
map), but what about the other two parameters?
Let me rephrase, what happens when we apply a function that takes three parameters to just one parameter? Exactly! A partial application!
Same happens when applying the operator
<!> to a function that takes three parameters, the difference is that the partially applied function gets boxed in an
Now we need to apply the boxed function to a boxed value, and for that we can use the operator
<*> that takes a boxed function and a boxed value and returns the boxed version of applying the function to the value.
In this case we have two more parameters so the full version would be:
Option to represent when a value may not be present has many advantages.
Not only is easier to deal with cases that produce no results, but also the code is clear an easy to follow.
Though here the code is in F# you could implement similar features in your favourite language.
All the code can be found here. I included a series of tests that show how the builder and applicative style apply a shortcircuit when one of the parameters is missing.
Thanks to my good friend Shane for helping me to test the code in all platforms.