True, False, ...Maybe?!
Among the cornerstones of computer science is the idea of boolean logic: true or false, 1 or 0, yes or no, on or off, and so on. Almost every programming language has some analogue to this, implemented in varying ways and with different syntax.
What is lesser known is that it is also possible to have logic with 3 values: often called three-valued logic, or trilean logic.
This logic preserves the true and false states, but also adds a third state: often
explained as “unknown,” or, “maybe.” This post was inspired by me stumbling upon
Haskell’s Maybe
type, and I thought it was interesting enough to do some further
investigation.
In the Haskell documentation, Maybe
was defined to be the following:
data Maybe a = Nothing | Just a
deriving (Eq, Ord)
As you can see, Maybe
is used when it is possible to have nothing, or something. But why,
you might ask, is this useful? Couldn’t we just use a null pointer?
Why null
sucks
First, let’s see what Tony Hoare, the inventor of null, has to say:
I call it my billion-dollar mistake. At that time, I was designing the first comprehensive type system for references in an object-oriented language. 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.
A lot more info is provided in his 2009 presentation,
Null References: The Billion Dollar Mistake
,
however the gist is here. The ease in which programmers can make mistakes while using null
,
especially if they believe a value is not null
, is extensive and has been proven time and time
again with security vulnerabilities throughout the ages.
However, the fundamental idea null
is trying to convey is often useful: the idea that a value
may not be there. Rust, in particular, solves this in an interesting way: with the Option<>
enum, which is basically an analogue to Haskell’s Maybe
:
enum Option<T> {
None,
Some(T),
}
Here, Option<>
can either be None
, or Some
value. Therefore, instead of having to checking
whether any variable is null or non-null, we can simply express this logic by wrapping our
values in Option<>
: which means that now, compilers can help us to avoid any null pointer
exceptions that have caused so much pain and suffering in this world.
An implementation
Python is an example of a language in which null is used: their None
. Here, we can try to
avoid that by creating a helper class that can implement some of this Maybe
logic we’ve been
talking about:
class Option:
def __init__(self, value=None):
self._value = value
def is_some(self):
return self._value is not None
def is_none(self):
return self._value is None
def unwrap(self):
if self.is_some():
return self._value
else:
raise ValueError("Option is None")
def unwrap_or(self, default_value):
return self._value if self.is_some() else default_value
Here, we have the option of unwrap_or
— and we provide a way for compilers to check whether
or not our code logic has the potential to create null pointer exceptions.