Gorm Casper1 January 2016

Destroy all utility libraries

I believe that the time of utility libraries are over. I’ve written my fair share of them (as exercises, and general introduction to new language features), but they seem more and more archaic and inflexible to me.

This feeling has only been strengthened in the past month where I have been working on an iPhone app in React Native where I, for uninteresting reasons, could not npm install anything (other than react-native of course). I did not miss utility libraries one bit.

Instead I write my own utility functions. Here is a map:

const map = (f, arr) => arr.map(f);

You could argue that these functions are easier when we are sure to have a .map defined on our data structure, but you’d be missing the point.

Here is another:

const map = curry((f, arr) => arr.map(f));

Or how about this one?

const map = (f, str) => str.split('').map(f).join('');

Who decided map should not work on strings? It does in Haskell where strings are arrays of characters. What about objects? This one maps a function over the values of an object:

const map = curry((f, obj) =>
  Object
    .entries(obj)
    .reduce((result, [key, value]) => {
      ...result,
      [key]: f(value)
    }, {});

Not useful you say? Well, it was useful back when I was working with {x, y} modelled coordinates (as opposed to [x, y]). Or this:

const dimensions = {
  width: 100,
  height: 50
};

const grow = map(multiply(2));

console.log(grow(dimensions));
→ {width: 200, height: 100}

How about my new favorite that works on anything iterable and returns an iterable itself, meaning we can use it for lazy style programming.

const map = function* (f, it) {
  for (const value of it)
    yield f(value);
};

Did you notice that I left out the index and the full iterable? Shouldn’t the third line be: yield f(value, index, it);? No, not really. I’ve written before about why this creates its own set of problems, but aren’t you using map incorrectly if you are relying on the item’s position in the array to determine its return value? Regardless, it is such inflexibility that leads to weird code.

If you look into the source code of the excellent lodash, then you’ll see all sorts of weird (and hard to read) speed optimizations because you should be allowed to pass in this as a third parameter and expect having it bound to the function, but at the same time not be hit by the performance penalty of having to bind this if you only pass in two parameters (array and function). You, however, can make any restrictions on yourself that you please.

Update: The source of lodash seems to have changed a lot since last time I looked at it, so this may not actually be true any more.

The core of the problem here, is that a general purpose map (or reduce, or you name it) can’t possibly account for all the stuff you want to be able to do with your functions without making it ridiculously complex and taking a performance hit of some sort or another. In some cases it is even impossible.

With the increase of JavaScript’s expressiveness I believe a general map or filter function simply does not cut it. Instead, write your own tools when you need them. Put them on github so that next time you need simple tools that you trust and deeply understand, they are only a git clone away.

Here are some of the tools I use and reuse.