DEV Community

JavaScript Joel
JavaScript Joel

Posted on • Edited on

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

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!

Top comments (20)

Collapse
 
deciduously profile image
Ben Lovy

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.

Collapse
 
joelnet profile image
JavaScript Joel

The decorator pattern is nice for sure. It would be cool JavaScript let you write it like this:

@maybe
const toupper = string => string.toUpperCase()

You can do that for methods on a class, but not standalone functions.

Hop over to the Discord chat and say hi!

Collapse
 
joelnet profile image
JavaScript Joel

Do you currently write functional JavaScript? What do you currently use?

Collapse
 
deciduously profile image
Ben Lovy

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.

Thread Thread
 
joelnet profile image
JavaScript Joel

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?

Thread Thread
 
deciduously profile image
Ben Lovy

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.

Thread Thread
 
joelnet profile image
JavaScript Joel • Edited

Whoa - no kidding! Congratulations. I'm definitely excited to play with it.

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 ;)

Thread Thread
 
deciduously profile image
Ben Lovy

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!

Thread Thread
 
joelnet profile image
JavaScript Joel

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...

vs code screenshot

Rich Boy dat meme face

This is a WIP so it'll only get better... :D

Thread Thread
 
deciduously profile image
Ben Lovy

...ok, whoa. That's some seriously cool stuff.

Thread Thread
 
joelnet profile image
JavaScript Joel

I think so... but you know, I am biased ;)

Collapse
 
kspeakman profile image
Kasey Speakman • Edited

Thanks for the post!

Very minor pedantic point about .NET Nullable. The current version of Nullable<T> can only be used with value types. Specifying string? 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 as int 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!

Collapse
 
joelnet profile image
JavaScript Joel

That's a lot of great info. It would be nice to have Option as an Option!

Collapse
 
fxedel profile image
Felix Edelmann • Edited

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 allow null for a certain variable, you use the nullable type (Int?, String?, ...).

When dealing with nullable variables, one can use the Elvis operator:

val name: String? = customer?.name
Enter fullscreen mode Exit fullscreen mode

name will be either a String or null if customer or customer.name is null. It won't throw if customer is null.

Collapse
 
joelnet profile image
JavaScript Joel • Edited

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!

Elvis Operator

Collapse
 
simonhaisz profile image
simonhaisz

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.

Collapse
 
joelnet profile image
JavaScript Joel • Edited

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.

Collapse
 
dmh2000 profile image
david howard

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?

Collapse
 
joelnet profile image
JavaScript Joel • Edited

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 a Just and a Nothing.

So you can stop guards completely!

This is a good example of what I am talking about:

const toUpper = string => string.toUpperCase()

map (toUpper) (Just ("abc")) //=> Just ('ABC')
map (toUpper) (Nothing) //=> Nothing

You don't have to worry about if the value is a Just or a Nothing. You just accept a Maybe.

Collapse
 
mcsee profile image
Maxi Contieri

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...