Tony Hoare, the creator of NULL, now refers to NULL as The Billion Dollar Mistake. Even though NULL Reference Exceptions continue to haunt our code to this day, we still choose to continue using it.
And for some reason JavaScript decided to double down on the problems with null
by also creating undefined
.
Today I would like to demonstrate a solution to this problem with the Maybe.
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. -- Tony Hoare
Do not underestimate the problems of NULL
Before you have even finish reading this article... I can already sense it, your desire to hit PAGE DOWN, rush straight to the comment section and blast out a "but NULL is never a problem for ME". But please pause, slow down, read and contemplate.
8 of 10 errors from Top 10 JavaScript errors from 1000+ projects (and how to avoid them) are null
and undefined
problems. Eight. Out. Of. Ten.
To underestimate NULL is to be defeated by NULL.
Null Guards
Because of the problems null
brings with it, we have to constantly guard our code from it. Unguarded code might look something like this:
const toUpper = string => string.toUpperCase()
This code is susceptible to NULL Reference Exceptions.
toUpper(null) //=> Cannot read property 'toUpperCase' of null
So we are forced to guard against null
.
const toUpper = string => {
if (string != null) {
// --------------
// \
// null guard
return string.toUpperCase()
}
}
But this quickly becomes verbose as everywhere that may encounter null
has to be guarded.
const toUpper = string => {
if (string != null) {
// --------------
// \
// duplication
return string.toUpperCase()
}
}
const toLower = string => {
if (string != null) {
// --------------
// \
// duplication
return string.toLowerCase()
}
}
const trim = string => {
if (string != null) {
// --------------
// \
// duplication
return string.trim()
}
}
If we think about a values as having a one-to-many relationship with code that may access it, then it makes more sense to place the guards on the one and not on the many.
Nullable Types
The .NET Framework 2.0 introduced Nullable Types into the .NET language. This new Nullable value, could be set to null without the reference being null. This meant if x
was a Nullable Type, you could still do things like x.HasValue
and x.Value
without getting a NullReferenceException
.
int? x = null
if (x.HasValue)
{
Console.WriteLine($"x is {x.Value}")
}
else
{
Console.WriteLine("x does not have a value")
}
The Maybe
The Maybe
is similar to a Nullable Type. The variable will always have a value, and that value might represent a null
, but it will never be set to null
.
For these examples, I'll be using the Maybe
from MojiScript. (Also checkout monet and Sanctuary, Folktale for other Maybes
). Use the following import:
import { fromNullable } from "mojiscript/type/Maybe"
The Maybe
is a union type of either a Just
or a Nothing
. Just
contains a value and Nothing
is well... nothing.
But now the value is all wrapped up inside of the Maybe
. To access the value of a Maybe
, you would have touse a map
function. Fun to Google: map
is what makes the Maybe
type a Functor
.
If you are getting that feeling that you have seen this somewhere before that is because this exactly how a Promise
works. The difference is Promise
uses then
and Maybe
uses Map
.
const promise = Promise.resolve(888)
const maybe = Just(888)
promise.then(double)
maybe.map(double)
Same same but different.
const toUpper = string => string.toUpperCase()
Just("abc").map(toUpper) //=> Just ('ABC')
Nothing.map(toUpper) //=> Nothing
Notice how in both cases above, the toUpper
function no longer throws an Error
. That is because we are no longer calling toUpper
directly with a String
, but instead mapping it with our Maybe
.
If we convert all types within our application to use a Maybe
, then all null guards are no longer necessary.
The null
is now guarded in a single place, in the Maybe
type, instead of being sprinkled throughout the application, wherever the value might be accessed.
The Maybe
is a guard on the one instead of the many!
Getting in and out of Maybes
But what about the times when we are not in control of the code, when we must send or receive a null
value? Some examples might be 3rd party libraries that will return a null
or libraries that will require passing null
as an argument.
In these cases, we can convert a null value to a Maybe using fromNullable
and we can convert back to a nullable value using fromMaybe
.
import { fromMaybe, fromNullable } from "mojiscript/type/Maybe"
// converting nullable values to a Maybe
fromNullable(undefined) //=> Nothing
fromNullable(null) //=> Nothing
fromNullable(123) //=> Just (123)
fromNullable("abc") //=> Just ("abc")
// converting Maybe to a nullable type
fromMaybe(Just("abc")) //=> 'abc'
fromMaybe(Nothing) //=> null
You could als guard a single function like this:
const toUpper = string =>
fromNullable(string).map(s => s.toUpperCase()).value
But that is a little verbose and it's much better to expand the safety of the Maybe type to the entire application. Put the guards in place at the gateways in and out of your application, not individual functions.
One example could be using a Maybe in your Redux.
// username is a Maybe, initially set to Nothing.
const initalState = {
username: Nothing
}
// your reducer is the gateway that ensures the value will always be a maybe.
const reducer = (state = initialState, { type, value }) =>
type === 'SET_USERNAME'
? { ...state, username: fromNullable(value) }
: state
// somewhere in your render
render() {
const userBlock = this.props.username.map(username => <h1>{username}</h1>)
const noUserBlock = <div>Anonymous</div>
return (
<div>
{fromMaybe (noUserBlock) (userBlock)}
</div>
)
}
JavaScript Type Coercion
MojiScript's Maybe
can use JavaScript's implicit and explicit coercion to it's advantage.
Maybe
can be implicity coerced into a String
.
// coercing to a String
console.log("a" + Just("b") + "c") //=> 'abc'
console.log("a" + Nothing + "c") //=> 'ac'
Maybe
can be explicity coerced into a Number
.
Number(Just(888)) //=> 888
Number(Nothing) //=> 0
Maybe
can even be stringified.
const data = {
id: Nothing,
name: Just("Joel")
}
JSON.stringify(data)
//=> {"id":null,"name":"Joel"}
Accessing Nested Objects
Let's take a look at the common task of accessing nested objects.
We'll use these objects. One is lacking an address, which can yield nulls
. Gross.
const user1 = {
id: 100,
address: {
address1: "123 Fake st",
state: "CA"
}
}
const user2 = {
id: 101
}
These are common ways to access nested objects.
user1.address.state //=> 'CA'
user2.address.state //=> Error: Cannot read property 'state' of undefined
// short circuit
user2 && user2.address && user2.address.state //=> undefined
// Oliver Steel's Nested Object Pattern
((user2||{}).address||{}).state //=> undefined
Prettier seems to hate both of those techniques, turning them into unreadable junk.
Now let's try accessing nested objects with a Maybe
.
import { fromNullable } from 'mojiscript/type/Maybe'
const prop = prop => obj =>
fromNullable(obj).flatMap(o => fromNullable(o[prop]))
Just(user1)
.flatMap(prop('address))
.flatMap(prop('state)) //=> Just ("CA")
Just(user2)
.flatMap(prop('address))
.flatMap(prop('address)) //=> Nothing
A lot of this boiler plate can be reduced with some helper methods.
import pathOr from 'mojiscript/object/PathOr'
import { fromNullable } from 'mojiscript/type/Maybe'
const getStateFromUser = obj =>
fromNullable(pathOr (null) ([ 'address', 'state' ]) (obj))
Just(user1).map(getStateFromUser) //=> Just ("CA")
Just(user2).map(getStateFromUser) //=> Nothing
Decoupled map function
A Map can also be decoupled from Maybe
. There are many libs that have a map
function, like Ramda, but I'll be using the one from MojiScript for this example.
import map from 'mojiscript/list/map'
const toUpper = string => string.toUpperCase()
Just("abc").map(toUpper) //=> Just ('ABC')
Nothing.map(toUpper) //=> Nothing
import map from 'mojiscript/list/map'
const toUpper = string => string.toUpperCase()
map (toUpper) (Just ("abc")) //=> Just ('ABC')
map (toUpper) (Nothing) //=> Nothing
This was getting far too big for this section, so it has been broken out into it's own article here: An introduction to MojiScript's enhanced map
Heavy Lifting
Lifting is a technique to apply Applicatives
to a function. In English that means we can use "normal" functions with our Maybes
. Fun to Google: ap
is what makes the Maybe
type an Applicative
.
This code will use liftA2
, A
for Applicative
and 2
for the number of arguments in the function.
import liftA2 from "mojiscript/function/liftA2"
import Just from "mojiscript/type/Just"
import Nothing from "mojiscript/type/Nothing"
const add = x => y => x + y
const ladd = liftA2 (add)
add (123) (765) //=> 888
ladd (Just (123)) (Just (765)) //=> Just (888)
ladd (Nothing) (Just (765)) //=> Nothing
ladd (Just (123)) (Nothing) //=> Nothing
Some things to notice:
- The function
add
is curried. You can use anycurry
function to do this for you. -
add
consists of 2 parameters. If it was 3, we would useliftA3
. - All arguments must be a
Just
, otherwiseNothing
is returned.
So now we do not have to modify our functions to understand the Maybe
type, we can use map
and also lift
to apply the function to our Maybes
.
Continue Learning: Functors, Applicatives, And Monads In Pictures does an incredible job of explaining this and more!
Maybe Function Decorator
There are times when you would like to guard a single function against NULL. That is where the maybe
Function Decorator comes in handy.
const maybe = func => (...args) =>
!args.length || args.some(x => x == null)
? null
: func(...args)
Guard your functions against null with the maybe
function decorator:
const toUpper = string => string.toUpperCase()
const maybeToUpper = maybe(toUpper)
maybeToUpper("abc") //=> 'ABC'
maybeToUpper(null) //=> null
Can also be written like this:
const toUpper = maybe(string => string.toUpperCase())
Learn more about Function Decorators:
- Function decorators: Transforming callbacks into promises and back again
- Functional JavaScript: Function Decorators Part 2
TC39 Optional Chaining for JavaScript
This is a good time to mention the TC39 Optional Chaining Proposal that is currently in Stage 1.
Optional Chaining will allow you to guard against null with a shorter syntax.
// without Optional Chaining
const toUpper = string => string && string.toUpperCase()
// with Optional Chaining
const toUpper = string => string?.toUpperCase()
Even with Optional Chaining, the guards are still on the many and not the one, but at least the syntax is short.
Wisdoms
- To underestimate NULL is to be defeated by NULL.
- 8 out of the 10 top 10 errors are NULL and undefined errors.
- If we think about a values as having a one-to-many relationship with code that may access it, then it makes more sense to place the guards on the one and not on the many.
- It is possible to completely eliminate an entire class of bugs (NULL Reference Exceptions) by eliminating
null
. - Having NULL Reference Exceptions in your code is a choice.
End
Have questions or comments? I'd love to hear them!
Hop over to the MojiScript Discord chat and say hi!
This turned out a little longer than I originally thought it would. But this is a subject that is hard to sum up into a single article.
You can also use the Maybe
with MojiScript's map
. Read more about how awesome MojiScript's map is here...
My articles are very Functional JavaScript heavy, if you need more FP, follow me here, or on Twitter @joelnet!
Top comments (20)
Great write-up! The languages whose implementations of
Maybe
I've tried don't seem have anything quite as handy as that decorator pattern. I think you've got a MojiScript convert.The decorator pattern is nice for sure. It would be cool JavaScript let you write it like this:
You can do that for methods on a class, but not standalone functions.
Hop over to the Discord chat and say hi!
Do you currently write functional JavaScript? What do you currently use?
I do not - all of the frontend projects I have worked on have been either ReasonML, ClojureScript, or Elm. I'm transitioning to TypeScript now and am on the prowl for best practices - this post was the first I'd ever heard of MojiScript.
MojiScript is new new new. Just created the "initial commit" on Aug 29!
I'm fascinated by ELM but have never used it. What are your thoughts on it?
Whoa - no kidding! Congratulations. I'm definitely excited to play with it.
I wanted to like Elm a lot more than I actually did. The language feels like a pared-down Haskell - PureScript is more interesting to me from a language perspective.
The Elm Architecture resonates with me, and I'm excited by the adaptations I'm seeing pop up for F# and Reason and PureScript.
It's got a bus factor of exactly 1, and 0.19 broke all my stuff. Breaking changes are to be expected with a a young project, but I'm just not quite sold on the ecosystem as a whole.
People do some really cool things with it, but I'm banking on the Architecture to bleed into other more widely used tools as opposed to Elm itself taking over the world.
Your feedback would be very beneficial in helping me drive the direction of MojiScript!
Man, that game boy emulator is insane...
Ahhh ya that was my fear. I love reading about ELM. It seems like everything I want. But then there's the issues with having to deal with JavaScript libraries. Because, let's face it, npm is what makes JavaScript great. Without libraries, I could use any language.
So interop with JavaScript was important to me.
That was a key decision to keeping MojiScript as JavaScript. There's just so much JavaScript already has, package management, build tools, community, browsers, node, etc.
Keeping the JavaScript comes with some limitations but it provides soooo much more that it becomes worth it.
Next up is to create an awesome development and debugging experience. I just merged some (what I think are) revolutionary debugging tools. More on that when I get time ;)
I'm very much a novice, but I'll absolutely be proactive about interacting with the project as I explore the tool. Looking forward to your next post!
Here's a sneak peek:
The
//?
is a command from Quokka that evaluates the code live. So to the right of the//?
code is a live evaluation of the value on the left.But look at that stack trace...
This is a WIP so it'll only get better... :D
...ok, whoa. That's some seriously cool stuff.
I think so... but you know, I am biased ;)
Thanks for the post!
Very minor pedantic point about .NET Nullable. The current version of
Nullable<T>
can only be used with value types. Specifyingstring?
will not compile. It basically lets value types (int, float, etc.) be null. Without this, they just map to byte values on the stack, not memory locations, so they can be zero but not null. This would not map very well to database columns such asint null
.However, nowadays a lot of devs have seen the value in assuming types are not null unless they are explicitly marked as having null as a valid value. So there is an effort underway to bring nullable reference types to C#. Another .NET language is F#. Since it was based on OCaml, it has had the
Option
type to represent any kind of object as explicitly "nullable" for a while.Cheers!
That's a lot of great info. It would be nice to have Option as an Option!
When we talk about
null
, we should also talk about Kotlin and its null-safety.First of all, all types (
Int
,String
, ...) are non-null by default. If you want to allownull
for a certain variable, you use the nullable type (Int?
,String?
, ...).When dealing with nullable variables, one can use the Elvis operator:
name
will be either a String or null ifcustomer
orcustomer.name
isnull
. It won't throw ifcustomer
is null.Non-null by default is the way it should be. I have never used Kotlin, but it sounds like they are doing things right.
I also love the term Elvis Operator!
Interesting article on the various ways to deal with this classic problem.
I've worked with a few people who were big proponents of the functional decorator pattern but I've always found it awkward to read/use. I just find the Nullable/Optional wrapper approach more natural.
Another method that works with Typescript is to add compile time checks by enforcing the explicit null rule. That way for something to be null there needs to be an explicit "| null" added to the type definition if the value could be null. It's a softer enforcement from all these other methods but at least it gets the developer thinking about null.
The decorator pattern is not as clean as it could be. I still use it but sparingly. Type checking is also a great option when available.
I get it that using Maybe prevents a null reference from crashing the program, but now you get a Nothing that's also unexpected. Is the philosophy then that you need to guard for Nothing or that it is just considered a lesser bug that needs to be fixed but at least doesn't kill the app. Or?
It changes from null being expected to. A Maybe being expected. So Nothing is a valid input.
When you use
map
, it will work on both aJust
and aNothing
.So you can stop guards completely!
This is a good example of what I am talking about:
You don't have to worry about if the value is a
Just
or aNothing
. You just accept aMaybe
.You article is great. I stole^H^H^H^H was inspired by some of your ideas to write this article
codeburst.io/null-the-billion-doll...