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.
- cherries: I literally copy/paste from this once in a while. This was the first library I built with this in mind.
- logger: The newest, but I am using something like this in a current project.
- iters.js & iters-fn: These are my goto repositories for iterator utilities.
- odd-helpers: Collection of helpers that you don’t normally find (like
fuzzySearch
andpadStart
). - audit: Least used. To be honest I’d forgotten about it.
- classname: Others seem to find this one useful too (of the three repositories I bothered adding to npm, this is the most downloaded one).