In the most recent issue of JS Weekly, a new piece from Eric Elliot about optional values in JavaScript reminded of one of the first Lambda Cast episodes I listened. #6: Null and Friends. At that point in time I was near the start of my functional programming research and finishing Kyle Simpson’s Functional-Light JavaScript. During the podcast I was delightedly shocked to hear the hosts discuss functional languages that are actually designed to keep null out of your programs. I could feel myself pulled toward the FP language hype, especially if that hype was about solving the real-world unpleasantness of working with ambiguity in code.

Because of JavaScript’s dynamic type system, optional values like null, undefined, or an empty string can easily make trouble in your software. The language lets us get away with saying I don’t know without any compose-time feedback. Therefore spinning up the browser and pushing play is often your only recourse to see how optionals are actually handled.

In his article, Elliot lists the common progenitors of null:

  1. User input
  2. Database/network records
  3. Uninitialized state
  4. Functions which could return nothing

Oh boy #3. I’ve got an example from the office, bear with me.

In our Event management application there is a form field. This field represents a maximum limit on the number of holds for an event ticket. There are two types of input this field can receive from a user: 1) an integer or 2) nothing (ie, be left empty). The latter signifies that the user desires to unrestrict the number of holds.

Nullable fields! These are quite commonplace and a user would never think twice about the dark alchemy we’re performing behind the scenes. What the user doesn’t know is that this field maps to a database columm that expects an integer (INT). Thus, when the user leaves the field empty and then submits the form, we need to make sure that an empty string is cast as a stringified representation of zero to slot in the POST body: {limit: 0}. Zero means unlimited. Leaving the field blank is not a lack, but bountiful! Nothing is not nothing. Logically sound, but conceptually wacky. A sensible default.

function serializeFormData(formData) {
  const limit = formData.limit || 0; // the empty input string is nullable!
  const nextField = //...
  const body = {
    limit,
    nextField,
    // ...
  }

  return body;
}

Of course, a reverse alchemy must occur from the other direction when our React code initializes the form to begin with. We first hand our form builder an empty object – key/value pairs with field names as keys and undefineds as values. The builder then converts these undefineds to appropriate defaults.

function getInitialFormData(initialData) {
  const limit = initialData.limit || '';
  const nextField = //...
  const initialFormData = {
    limit,
    nextField,
    // ...
  }

  return initialFormData;
}

Looks familiar.

Thusly, the JavaScripter putzes around with a notion of nothingness by phase-shifting duck typed values.

null <-> '' <-> 0

(There’s probably cooler mathematical notation for this.)

Unforch we have to use null and friends for lack of a better optional option. Like, for lack of formal invariants in-language. It’s impossible to truly prevent these meaningless nothings from entering our JavaScript programs. (Meaningless like may never receive meaning, ambiguous, undecided. Totally void 0: what a good euphemism from the grammar.) Like, you can’t serialize nothing for a value an API response formatted as JSON.

>>> json.dumps({name: }
  File "<stdin>", line 1
    json.dumps({name: }
                     ^
SyntaxError: invalid syntax

Or try and stringify an Object back up again with the same:

>> JSON.stringify({name: })
SyntaxError: expected expression, got '}'

Null and undefined are optional in JS but they are not illegal. Like in Haskell, which wraps up the ambiguity in a fat Nothing.

Elliot does an interesting rhetorical jiu jitsu by giving us new options for optional values. In liue of eviscerating null from JS, we can work to push null to the edge of our programs with a handful of innovative approaches. In a sense we can ignore nullables and declutter areas of code which can just focus on data pipelining and other UI biz logic. Techniques include: constructing state machines – highly determined object interfaces – that error without values set to a wanted data type; ie something. We can also take advantage of that new new: Optional Chaining. And then there’s borrowing from FP. The last I love.

I’ve already been thinking about Maybes a lot recently. My last post was about using “maybe” in function names to negotiate the unrealistic binary of if/else with better signaling to other developers; like, a function may or may not complete it’s designated purpose if an async call fails or an argument is unacceptable. The real word is far too fuzzy. In contrast to the imperatives of JS, FP languages substitute nullable data with algebraic structures that encapsulate possibilities of existence or nothingness. For example, the actual Maybe data type represents either a Just (truth, success, completion) or Nothing (false, error, incompatibility). Data that’s wrapped in a Maybe and operated on won’t leak a nullable into our program, like the commonly observed undefined. Obviously implementations vary across libaries. Here’s a simple example from the Practica library which demonstrates the way that using Maybe can simplify code:

import { Maybe } from 'pratica';

const data = await fetchAllPeople(...);

Maybe(data)
  .map(people => people.filter(person => person.cool))
  .map(people => people.map(getNames))
  .map(names => name.toUpperCase())
  .cata({
    Just: transformedData => render(transformedData),
    Nothing: () => console.log('Womp, no data returned from API.')
  })

(Btw, “cata” stands for catamorphism and means to decompose the Maybe container into simple values. Honestly, I’m not good enough in the category theory yet to confidently distill it for you completely – pun intended – but that’s the gist.)

A more basic JS solution might look like:

const data = await fetchAllPeople(...);

if (data) {
    const coolPeople = people => people.filter(person => person.kind);

    if (coolPeople.length) {
        const formattedKindPeople = people => people.map(formatPersonForDisplay);
        render(formattedKindPeople)
    } else {
        console.log('Womp, only unkind people.')
    }
} else {
    console.log('Womp, no data returned from API.')
}

The combination of FP-style data pipelining – allowed by Maybe’s monadic interface, I think? – and control flow encapsulated in the data type itself, we get a semantically rich and easy-to-read solution without nullables and exhausting boilerplate; ie, param existence checks.

Where Elliot really surprised me was drawing a line between FP’s similar-to-Maybe data type Either and JS’s Promise. Tucking null away with Promises is super neat. Let’s see how that plays out in a sec.

While maybes represent one or no value, Just or Nothing, Either implementations are slightly different in that they represent one or the other, but not both. If you’re familiar with bitwise XOR, it’s the same algorithm, except that in place of a Nothing or performing a noop, Eithers provide a secondary branch for an error case. Let’s see it in action.

Take Elliot’s example of a small abstraction that hides null checking away in a kind of promisified ternary (which I’ve slightly modified):

const exists = (x) => x !== null;
const ifExists = (value) =>
  exists(value)
    ? Promise.resolve(value)
    : Promise.reject(`Invalid prop: ${value}`);

ifExists(prop.name).then(renderName).catch(log);

Here basic null checking and *primitive" if/else binaries are replaced with a more expressive, semantically rich statement for the logical disjunction: proceed this way if success, or that way.

Now, logging an error doesn’t get us very far from param checking and early returns. A slightly more interesting example might be something like:

const inputExists = x => x !== '';
const ifInputExists = value => inputExists(value) ?
  Promise.resolve(value) :
  Promise.reject(`Input is blank`);

onInput((prevValue, nextValue) =>
    ifInputExists(nextValue)
        .then(validate)
        .catch(trackClearInput(prevValue))

It’s hard to see the real power of this for a simple resolve/reject example. It just feels like a fancy if/else, right? But if we extrapolate from this base interesting things start to happen. Here’s a slightly modified version of an example from Practica’s docs with an imaginary Either that uses Promises under the hood and implements a chain behavior:

const isPerson = p => p.name && p.age
  ? Promise.resolve(p)
  : Promise.reject('Not a person')

const isOlderThan2 = p => p.age > 2
  ? Promise.resolve(p)
  : Promise.reject('Not older than 2')

const isJason = p => p.name === 'jason'
  ? Promise.resolve(p)
  : Promise.reject('Not jason')

const person = awaitfetchPerson(...);

Either(person)
  .chain(isPerson)
  .chain(isOlderThan2)
  .chain(isJason)
  .then(p => console.log('this person satisfies all the checks'))
  .catch(msg => console.log(msg)); // if any checks reject, then this function will be called. If isPerson rejects, then isOlderThan2 and isJason functions won't even execute, and the err msg would be 'Not a person'

Suffice to say I’m quite tickled by the re-purposing of Promises as Eithers. You can start to imagine how one might construct custom-fit control flow chains and layer cakes using thenable types to play nicely with other function composition and pipelining. I’m not always in love with (what feels like) sacrificed readability with chaining over stacked lines of assigned returns or async/await. But seeing it an action using Practica I’m starting to believe more and more in its viability, even in a codebase touched by less experienced or new developers.

Gripes with FP readability aside, it’s eye-opening to look at available JS language features and see them in a different light. Also, aside from the clever use of Promises, just getting the null check into an abstraction exists(...) already has us using an FP mindset to build strong declarative (function-first) foundations.