Special Null Syntax vs. Types and Functions

Why does Maybe.map feel better than ?? and friends to me? A sketch.

Assumed audience: Software developers, especially those with any interest in types, programming languages, and the intersection of the two.

Epistemic status: Basically thinking out loud about a thing I observed this weekend. I think I affirm the thing I took out of this experience.

I have been out of my cage phase with typed functional programming for a while now1 but there is a thing I still end up thinking about a lot (just now: because of doing some tweaks to my static site generator and using True Myth’s Maybe): The combination of a Maybe type and associated functions to work with them are just so much more general than special-case null-handling syntax like the optional chaining ?. or nullish coalescing ??, etc. operators which JavaScript, C#, and many other languages have sprouted over the past few years.

That generality is true in at least two ways, one of which gets commented on a lot in the things I read, and one of which does not. The one which gets commented on a lot is the way that map() is the same basic operation whether it’s Maybe or Array or Task or something else: functors! applicatives! monads! category theory!

The one that gets commented on less is that a type with function operating over it is just plain more useful. I built a little function2 for my static site generator today which did some string handling, and where I ultimately ended up using True Myths Maybe type. I implemented it initially with a null object” pattern: just returning an empty string for the case where its arguments did not have a sensible expansion. It was annoying to work with in different contexts, though; and so was returning null, which I also tried. Just returning a Maybe<string> instead of doing either a null return or a '' default case” return let me just .map() over the result and do different things in different contexts as appropriate, though. That was impossible with '' and difficult with null, even with nullish coalescing or optional chaining, because the subsequent operations were shaped like functions, and the syntax doesn’t help with that.

It turns out that functions and types compose really nicely in a way that dedicated syntax does not (necessarily). This does not mean that dedicated syntax is bad — Lisp notwithstanding — so much as that specialization and generality do actually sit in tension with each other.


Notes

  1. …though as with my Calvinism, that is mostly a function of it having become part of the bedrock of my thinking, and not at all because my affirmation has faded in intensity: to the contrary, even. ↩︎

  2. It uses JavaScript’s recent Intl.ListFormat API to make nicely separated lists for display, e.g. to take in ["cool", "neat", "awesome"] and produce "cool, neat, and awesome". ↩︎