NULL, "The Billion Dollar Mistake", Maybe Just Nothing

JavaScript Joel - Oct 30 '18 - - Dev Community

Joker burning a huge pile of money

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()
Enter fullscreen mode Exit fullscreen mode

This code is susceptible to NULL Reference Exceptions.

toUpper(null) //=> ​​Cannot read property 'toUpperCase' of null​​
Enter fullscreen mode Exit fullscreen mode

So we are forced to guard against null.

const toUpper = string => {
  if (string != null) {
//    --------------
//                   \
//                    null guard
    return string.toUpperCase()
  }
}
Enter fullscreen mode Exit fullscreen mode

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()
  }
}
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Same same but different.

const toUpper = string => string.toUpperCase()

Just("abc").map(toUpper) //=> Just ('ABC')
Nothing.map(toUpper) //=> Nothing
Enter fullscreen mode Exit fullscreen mode

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!

Neo vs many agent Smiths

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
Enter fullscreen mode Exit fullscreen mode

You could als guard a single function like this:

const toUpper = string =>
  fromNullable(string).map(s => s.toUpperCase()).value
Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

Maybe can be explicity coerced into a Number.

Number(Just(888)) //=> 888
Number(Nothing) //=> 0
Enter fullscreen mode Exit fullscreen mode

Maybe can even be stringified.

const data = {
  id: Nothing,
  name: Just("Joel")
}

JSON.stringify(data)
//=> {"id":null,"name":"Joel"}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
import map from 'mojiscript/list/map'

const toUpper = string => string.toUpperCase()

map (toUpper) (Just ("abc")) //=> Just ('ABC')
map (toUpper) (Nothing) //=> Nothing
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Some things to notice:

  • The function add is curried. You can use any curry function to do this for you.
  • add consists of 2 parameters. If it was 3, we would use liftA3.
  • All arguments must be a Just, otherwise Nothing 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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Can also be written like this:

const toUpper = maybe(string => string.toUpperCase())
Enter fullscreen mode Exit fullscreen mode

Learn more about Function Decorators:

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()
Enter fullscreen mode Exit fullscreen mode

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!

Cheers!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .