Skip to content

Instantly share code, notes, and snippets.

@robinpokorny
Created July 24, 2021 12:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robinpokorny/01019f85dc41fe6196f957a9d6c89480 to your computer and use it in GitHub Desktop.
Save robinpokorny/01019f85dc41fe6196f957a9d6c89480 to your computer and use it in GitHub Desktop.

When I was working on my small side-project library, I needed to represent a missing value. In the past, I'd used the nullable approach in simple settings and Option (aka Maybe) when I wanted more control.

In this case, neither felt correct so I came up with a different approach I'd like to present.

Why Nullable was not enough

Nullable means that when there is a value it is a string, a number, or an object. When there is no value, we use either null or undefined.

Tip: if you work with nullable types in TypeScript, make sure you turn on the strictNullChecks

This is often fine.

There are, in general, two cases when it's not:

  1. The value can be null or undefined. In the end, these are both valid JavaScript primitives and people can use them in many ways.
  2. You want to add some advanced logic. Writing x == null everywhere gets cumbersome.

In my case I was handling an output of a Promise, that can return anything. And I could foresee that both of the ‘missing’ will be eventually returned.

In general, the problem 1 and 2 have the same solution: use a library that implements the Option type.

Why Option was too much

Option (sometimes called Maybe) type has two possibilities: either there is no value (None on Nothing) or there is a value (Some or Just).

In JavaScript/TypeScript this means introducing a new structure that wraps the value. Most commonly an object with a property tag that defines what possibility it is.

This is how you could quickly implement Option in TypeScript:

https://gist.github.com/cf6165a467f02320ba648cf873520688

Usually, you would use a library that defines the type and a bunch of useful utils alongside. Here is an intro to Option in my favourite fp-ts library.

The library I was building was small, had zero dependencies, and there was no need for using any Option utility. Therefore, bringing in an Option library would be overkill.

For a while I was thinking about inlining the Option, that is coding it from scratch. For my use case that would be just a few lines. It would complicate the logic of the library a bit, though.

Then, I had a better idea!

Symbol as the new null

Coming back to Nullable, the unsolvable problem is that null (or undefined) is global. It is one value equal to itself. It is the same for everybody.

If you return null and I return null, later, it is not possible to find out where the null comes from.

In other words, there is ever only one instance. To solve it, we need to have a new instance of null.

Sure, we could use an empty object. In JavaScript each object is a new instance that is not equal to any other object.

But hey, in ES6 we got a new primitive that does exactly that: Symbol. (Read some introduction to Symbols)

What I did was a new constant that represented a missing value, which was a symbol:

https://gist.github.com/b3dab222be2508905bbe77d8e5360dce

Let's look at the benefits:

  • It is a simple value, no wrapper needed
  • Anything else is treated as data
  • It's a private None, the symbol cannot be recreated elsewhere
  • It has no meaning outside our code
  • The label makes debugging easier

That is great! Especially the first point allows using None as null. See some example use:

https://gist.github.com/83ceafb4cc7dd8698d1fdc9ad69ba980

Symbols are almost nulls

There are some disadvantages, too.

First, which is IMO rare, is that the environment has to support ES6 Symbols. That means Node.js >=0.12 (not to be confused with v12).

Second, there are problems with (de)serialisation. Funnily, Symbols behave exactly like undefined.

https://gist.github.com/874c2b57405d08ddcbb7f8c1e02ceb59

So, the information about the instance is, of course, lost. Yet, since it then behaves like undefined—the native ‘missing value’)—makes it well suited for representing a custom ‘missing value’.

In contrast, Option is based on structure not instances. Any object with a property tag set to none is considered None. This allows for easier serialisation and deserialisation.

Summary

I'm rather happy with this pattern. It seems it's a safer alternative to null in places where no advanced operations on the property are needed.

Maybe, I'd avoid it if this custom symbol should leak outside of a module or a library.

I especially like that with the variable name and the symbol label, I can communicate the domain meaning of the missing value. In my small library it represents that the promise is not settled:

https://gist.github.com/b5bdacc78b10de37709fdf3980de5274

Potentially, there could be multiple missing values for different domain meanings.

Let me know what you think of this use? Is it a good replacement for null? Should everybody always use an Option?

Note: Symbols are not always easy to use, watch my talk Symbols complicated it all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment