Blog is available in backup mode, go to github-pages for newer posts

Back to articles list

It's dangerous to go alone. Take reduce.

All our attempts to achieve ideal code are actually about trade-offs. Unfortunately, sometimes one is influenced by hype.

I'd like to talk about reduce, which is gaining popularity with various libraries and whatnot. Redux, which is a "state container" based mostly on reduce abstraction. Another example is transducers, which made their way into JavaScript from closure. Incidentally, I consider the implementation no more than just a trade-off: you gain in performance (less GC thrashing), but lose a little in readability.

These are cool things and they have their applications in some cases, but reduce abstraction comes with a cost and shouldn't be used just because it's cool.

  1. Reduce is hard to comprehend. Not nearly as hard as e.g. monads (which is burrito), but such operation isn't what developers use often in their work and hence it's not JIT-optimized in their brains.

  2. Reduce in JavaScript is hard to parse. Of course, I'm talking about parsing by meatware. Normally we read JavaScript code from left to right thanks mostly to chaining, promoted in front-end land by jQuery (which is monad and therefore a burrito). But initial value for the reduce function is the last argument. This is especially a problem when reduce is deeply nested inside the code. Let's look at the code from a recent jstip.


var combineTotalPriceReducers = function(reducers) { // (1)
  return function(state, item) { // (2)
    return Object.keys(reducers).reduce( // (3), ouch this is a reduce
                                         // let's look for initial value
      function(nextState, key) { // (5)
        reducers[key](state, item); // (6)
        return state; // (7)
      },
      {} // (4) here it is, let's go back
    );
  }
};

Such jumps drastically reduce (no pun intended) our ability to quickly look through the code, which is important. If you're not convinced – think: what is the most infamous thing in programming? I'd say, goto, which requires us to leap through the code like an injured frog. From reading perspective reduce pattern might be as bad as a couple of gotos.

So back to the example. Here reduce is abused. Return value is discarded. Twice: (6) and (7). On top of that, neither nextState (5) nor initial value (4) is used. It's just as unsemantical as using map instead of forEach, for bare iteration.

Actually the code from the js-pieceof-tip could be rewritten as:


let addPrice =
  (currency, exchangeRate) =>
    (state, item) => state[currency] += item.price * exchangeRate;
visitors = [ // rewritten to array for simpler iteration, still easy to read
  addPrice('dollars', 1),
  addPrice('euros', 0.897424392),
  addPrice('pounds', 0.692688671),
  addPrice('yens', 113.852)
];

let applyAllPriceVisitors = (visitors) =>
  (state, item) => visitors.forEach(visitor =>
    visitor(state, item));

applyAllPriceVisitors(visitors)
  ({dollars: 0, euros:0, yens: 0, pounds: 0}, {price: 10});

"Visitor" might be not the best name here, but neither is reducer.

So when to use reduce considering all its disadvantages? In functional programming where you have only pure functions and passing values through the chain of reduce's callback is the only way to keep state. If we have used immutable collections, then the example can be written as:


let addPrice =
  (currency, exchangeRate) =>
    (state, item) => state.update(currency, v => v + item.price * exchangeRate);
reducers = [ // rewritten to array for simpler iteration, still easy to read
  addPrice('dollars', 1),
  addPrice('euros', 0.897424392),
  addPrice('pounds', 0.692688671),
  addPrice('yens', 113.852)
];

let applyAllPriceReducers = (reducers) =>
  (initial, item) => reducers.reduce(
    reducer => reducer(state, item),
    initial);

applyAllPriceReducers(reducers)
  (Immutable.Map({dollars: 0, euros:0, yens: 0, pounds: 0}), {price: 10});

BTW partially applied applyAllPriceReducers(reducers) is itself a suitable pure function for reducer. But this is another story.

Don't use reduce just because it's cool. Tame the hype. Learn your tools and use appropriately.