Chaining Is Not The Answer

Peter Jones
5 min readAug 1, 2016

--

I’m a Javascript developer, and one thing the Javascript community is known for is sudden and explosive adoption of thrilling new programming paradigms, ten or fifteen years after everyone else discovered them.

  • It happened with higher order functions like map/filter/reduce, which only really became popular after lodash/underscore gave us that whole wider suite of functionality.
  • It happened with promises, which not a lot of people understood when jquery gave us the original tiny version, followed by dozens of massively parallel independent implementations, followed by rapid standardization.
  • It happened with modules, big time. Everybody thought YUI’s module system was bringing Java to Javascript (then, as now, the gravest sin imaginable), but now in 2016 even require.js, a library that achieved high adoption doing that and only that, looks worn out and used up. AMD? Is that like webpack?
  • It’s going to happen with generators (some performance help from interpreters would be nice) and async/await (which as your coworkers will tell you is hipster bullshit that no one needs, right up until they realize they’re burned out on structuring their code around the need to write `.then` a million times).

One idea that’s become popular with well designed libraries is chainable interfaces. Is anyone out there old enough to have recommended adopting jquery at work? Think back: what did you show them as evidence that it solved a problem elegantly? Maybe you showed off how it encapsulated browser-specific behavior or APIs, like with the ajax methods; or you might have wowed them with the CSS selectors (assuming they weren’t already using Prototype); or maybe depending on when this was you pointed to the huge library of plugins. All great, all points in its column. But I bet before it was adopted, at least one of your colleagues had their breath taken away by something like the following:

$("#divTest2")
.text("Hello, world!")
.removeClass("blue")
.addClass("bold")
.css("color", "blue");

(from http://www.jquery-tutorial.net/introduction/method-chaining/)

Chaining: So elegant! So declarative! Gone are the days of wasting keystrokes and cranial cycles making up variable names for every little step and then potentially typing them wrong on the next line. Just look at that block: a bulleted list of operations one after the other, a bullshit-free paragraph of code, a complex operation specified as a list of smaller operations. Bash comes to Javascript! Bliss!

So jquery showed us that we wanted this thing called chaining, and then underscore/lodash showed us how far you could take it. Along with their own flavor of abstracting away details (nobody wants to spend time thinking about whether their data is an array or an object or a NodeList or whatever `arguments` is), they gave us this insanely sweet arsenal of operations that could do just about anything, even if you’re not touching the dom. You know the first time you used pluck you felt like a general ordering thousands of peons to do your bidding. And it can apply to any data anywhere! mapValues, groupBy, zipObject, range! I point my finger and there’s a mountain, point again and there’s a waterfall! It’s intoxicating! I personally am a level 12 lodash user, I never type a semicolon twice in one file.

I’m being a little facetious, but it really is awesome. I can write code that’s tremendously dense with meaning and yet simple to read. That’s huge! Pulling off both of those at the same time is a great achievement.

But there’s a dark side too. Jquery is chainable, and lodash is chainable, and Lazy.js, and promises, and the array methods, and all the rest of them. And even if not, there are ways! But none of them are chainable into each other. No jquery method will ever return an object that you can call an underscore method on. We didn’t realize it ten lines ago when we started chaining, but we were committing to one silo of methods, and if we want to use another silo then we have to leave this Garden Of Eden and take the tram over to the next one. Now, that’s not so bad! We’ve got two Gardens Of Eden, we should count our blessings! But it does feel incomplete. I’m reminded of my college days in the frozen northeast: it’s not that I was ungrateful to get to sit in a nice warm lecture hall, watching the fog melt off my glasses, but it still sucked having to trudge back out into it for the next class.

So to sum up thus far: we like having a way to express chains of operations that doesn’t require declaring a variable just to use it on the next line and then never again. But we don’t like being stuck in one context, one ecosystem, with no way out except to resort to the old, bad, dumb way mentioned above.

What we’d like to write

If I could just make up an operator out of thin air and use it to solve my problem, it would look like this:

$('article[data-source-uri]')      // jquery
|> _.pluck('dataset.sourceUri') // now over to lodash
|> _.map($.get) // jquery with help from lodash
|> _.invoke('then', JSON.parse) // lodash + promises
|> Promise.all // promises again
// ......

Now for some good news

The good news is I lied! I didn’t make up that operator, it already exists. Or at least it’s on the specification track. It’s the pipeline operator. As you can read at the proposal page, it’s already in use in several languages, including hip ones like Elixir and Elm.

I believe that pipelining, more than chaining, is how we’ll be constructing blocks of logic in the near future, simply because it’s more portable.

For their part, lodash has already gotten in front of this. The above example won’t work in traditional lodash where the data argument comes first, but it does work with lodash/fp. The history of Javascript has been a story of, among other things, an increasing move toward more functional techniques, so this makes sense for them.

This is hipster bullshit, I don’t need it

And yet I bet you use chaining when your library authors deign to provide it for you.

Play around with it

@mindeavor, who made the proposal, has also published a Babel plugin, so you can start using it now.

There’s one more piece that’s needed to help the pipeline operator live up to its potential. It’s the bind operator and I’ll talk about it in my next piece.

EDIT: Reddit user /u/laggingreflex fact checked me: the plugin linked above will not work (yet), the proposal is held up politically. While we wait, if the style interests you, then try writing something like the following:

// Pipeline function to give us our own pipeline operator
const pipeline = (value, ...fns) => {
for (const fn of fns) {
value = fn(value);
}
return value;
};
// Inside a pipeline call, the pipeline operator is comma
pipeline($('article[data-source-uri]')
, _.pluck('dataset.sourceUri')
, _.map($.get)
, _.invoke('then', JSON.parse)
, Promise.all
// ......
);

It’s not as nice to read or write but it’ll let you get started.

--

--