Seeking refuge from unsafe JavaScript

Updated on November 21, 2018

In late 2013, the vast majority of Plaid's code was JavaScript. Though the proportion has decreased since then, JavaScript still accounts for more than half our code.

Early Plaid code was fairly typical imperative JavaScript. For example:

module.exports = function(raw)
  var o = {};

  for (var i = 0; i < raw.values.length; i++) {
    if (raw.values[i].id === 'date') = Date.create(raw.values[i].value);
    else if (raw.values[i].id === 'number')
      o.number = parseFloat(raw.values[i].value)*-1

  return o;

Soon after I joined we began using Underscore in our various projects.
We learned to always provide {} as the first argument to _.extend to
avoid unintentional mutation. We accepted the fact that _.chain worked
with Underscore functions but not functions defined elsewhere. We tolerated
Underscore's inconvenient argument order, and even added placeholder
support to _.partial
to make point-free programming with Underscore
possible (though not natural). Using Underscore was clearly better than
using no library at all. We were contented.

Underscore saved us from writing for loops, but did nothing to fix our type

And we had many type errors.

We were able to avoid some type errors by using a linter and incorporating
linting into our pull request workflow, but a significant proportion of type
errors cannot be detected statically in a language as dynamic as JavaScript.
Improving our test coverage further mitigated regressions.

Even after mitigating type errors caused by human error, many type errors
still resulted from inconsistent data. At Plaid we process data from many
disparate sources, but even for a single source the data's shape may vary.
We were bitten by this several times. Take the following expression:

This looked innocuous, and the test suite passed. We deployed, and then noticed
this in the production error logs:

TypeError: Cannot read property 'baz' of undefined

Ah. So could be undefined in some cases, apparently. So: &&

Fixed? We then learned that was undefined in some cases.
We resorted to using guards:

data && && &&

As Underscore became an ever more important ingredient in writing JavaScript
programs at Plaid, we grew tired of function expressions cluttering our code
(arrow functions were not available to us at the time). To define a function
that sums a list of numbers, one might write the following in Haskell:

foldl (+) 0

With Underscore, one might have written:

_.partial(_.reduce, _, function(a, b) { return a + b; }, 0)

As a side project I created Nucleotides, a tiny library which makes every
JavaScript operator available as a function. We could then write:

_.partial(_.reduce, _, nucleotides.operator.binary['+'], 0)

This was still much less clear than the Haskell equivalent.

One day Graeme Yeates mentioned me on a Ramda issue. It was my first
exposure to Ramda, and I was impressed by its terseness:

R.reduce(R.add, 0)

Elegant! Like the Haskell definition, it takes advantage of currying.

Several of us at Plaid caught the Ramda bug, and Plaid became one of the
first companies to use Ramda in production. No longer did we need to worry
about functions mutating their arguments, as Ramda functions never did.
No longer was _.partial necessary to define specialized functions
in terms of more general ones, as every Ramda function has support for
partial application baked in.

Once again, we were contented.

I then began to worry about Ramda's unsafe functions. R.head,
for example, is of type [a] → a. When applied to [], there is no a,
so Ramda returns undefined. This means evaluating an expression such as
R.toUpper(R.head(xs)) will result in a run-time exception if xs is [].

This problem can be resolved by changing the type of head to [a] → Maybe a.
If head is applied to [] the result is Nothing(), which indicates a
failed operation. If head is applied to ['x', 'y', 'z'] the result is
Just('x'). The head of the list, 'x', is wrapped in a container which
indicates a successful operation.

Nothing() and Just('x') are both members of the Maybe String type. Both
values support exactly the same set of operations. To transform the String
which may be inside the Maybe, we use map:

                   +------+                 +--------------+
['x', 'y', 'z'] ~~~| head |~~> Just('x') ~~~| map(toUpper) |~~> Just('X')
                   +------+                 +--------------+

                   +------+                 +--------------+
             [] ~~~| head |~~> Nothing() ~~~| map(toUpper) |~~> Nothing()
                   +------+                 +--------------+

To quench my thirst for type safety I defined safe versions of several unsafe
Ramda functions (including head). Initially these lived in a file of helper
functions in one Plaid project. We realized these would be useful in other
projects and to people outside the company, so we released Sanctuary on
GitHub and npm. Now, with Ramda and Sanctuary, it's possible to write terse,
declarative programs that work correctly for all inputs.

S.pipe([S.gets(String, ['transaction_info', 'amount']),

This function, of type Object → Tx → Tx, describes a sequence of
transformations to safely extract a particular value from an Object,
and possibly update the value of the amount field of a Tx value.

It acknowledges the following possibilities:

  • the transaction_info field may be absent;
  • the value of the transaction_info field may be null or undefined;
  • the amount field may be absent;
  • the value of the amount field may not be of type String
    (the argument type required by N.normalizeAmount); and
  • the value of the amount field may not actually represent an amount.

It does so with the Maybe data type rather than with
incoherent guards and exception handling.

Although Sanctuary was initially developed internally, several people from
outside Plaid have become valued collaborators since we released the project
under the MIT license. Stefano Vozza was the first external contributor
(documenting much of what was a completely undocumented API at the time), and
remains one of the most active. Kevin Wallace contributed the wonderful
multi-line error messages:

S.fromMaybe(0, S.Just('XXX'));
// ! TypeError: Type-variable constraint violation
//   fromMaybe :: a -> Maybe a -> a
//                ^          ^
//                1          2
//   1)  0 :: Number, FiniteNumber, Integer, ValidNumber
//   2)  "XXX" :: String
//   Since there is no type of which all the above values are members, the type-variable constraint has been violated.

In recognition of the fact that the Sanctuary community is now self-sustaining,
we've transferred the repositories to the sanctuary-js organization on

I believe these projects have an important role to play in the future of
functional programming in JavaScript, and in exposing JavaScript programmers
to ideas from other languages. I'll continue to work alongside other members
of the community to improve our refuge from unsafe JavaScript.

Integrate Today