16

A while ago, I posted a question on StackOverflow showing that the native implementation of reduceRight in JavaScript is annoying. Hence, I created a Haskell-style foldr function as a remedy:

function foldr(array, callback, initial) {
    var length = array.length;

    if (arguments.length < 3) {
        if (length > 0) var result = array[--length];
        else throw new Error("Reduce of empty array with no initial value");
    } else var result = initial;

    while (length > 0) {
        var index = --length;
        result = callback(array[index], result, index, array);
    }

    return result;
}

However, I never used this foldr function simply because I never needed to iterate over an array from right-to-left. This got me thinking, why don't I use foldr in JavaScript as much as I do in Haskell and what are some real world examples of using foldr in JavaScript?

I could be wrong, but I believe that the foldr function is used widely in Haskell because of:

  1. Lazy Evaluation (foldl is tail recursive, so how come foldr runs faster than foldl?)
  2. Short cut fusion using foldr/build (Correctness of short cut fusion: foldr/build)

This would explain why foldr or reduceRight are not widely used in JavaScript. I have yet to see a real world use of foldr only for its right-to-left iteration order.

This brings me to my two questions:

  1. What are some real world examples of using reduceRight in JavaScript? Perhaps you have used it in an npm package. It would be great if you could link me to your code and explain why you needed to use reduceRight instead of reduce.
  2. Why is reduceRight not used as widely as reduce is in JavaScript? I already provided my two cents on this matter. I believe that foldr is primarily used only for its laziness, which is why reduceRight is not very useful in JavaScript. However, I could be wrong.

For the first question, I tried to find some real world examples for using reduceRight in JavaScript. However, I didn't find any satisfactory answers. The only examples I found were trivial and theoretical:

when to use reduce and reduceRight?

What I am looking for is a practical example. When is it practical to use reduceRight in JavaScript instead of reduce?

For the second question, I understand that it is primarily opinion based which is why it's alright if you don't answer it. The main focus of this post is on the first question, not the second.

Community
  • 1
  • 1
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • I don't think `foldr` function in haskell traverses from right-to-left ? – Sibi Feb 19 '15 at 12:59
  • 1
    @Sibi You're right. It's not possible to traverse a list from right-to-left in Haskell because of the way a list is defined (i.e. `data [a] = [] | a : [a]`). The `foldr` function in Haskell only accumulates a result from right-to-left. However, this question really isn't about Haskell per se. – Aadit M Shah Feb 19 '15 at 13:02
  • 1
    I have literally never used `reduceRight` in code. I've used `reduce` at least a thousand times. – Benjamin Gruenbaum Feb 19 '15 at 14:56
  • 1
    Hm, maybe implement some kind of `reverse` with it? – Bergi Feb 19 '15 at 23:49
  • i recently did a code survey of all the cdn.js scripts. it found 144 uses of reduceRight compared to 654 uses of reduce. sadly, i threw away the data (took 2 hours to delete all the scripts on a fast box) or else i would find you specifics, but they should be out there if reduceRight is 1/4th as used... – dandavis Feb 26 '15 at 18:53
  • @Aadit I added another use case for nested computations/data structures –  Feb 18 '17 at 12:17

6 Answers6

10

To answer your first question, reduceRight comes in pretty handy when you want to specify items in a left-to-right manner but execute them in a right-to-left manner.

Consider this naive implementation of a compose function that takes arguments left-to-right but is read and executed right-to-left:

var compose = function () {
    var args = [].slice.call(arguments);

    return function (initial) {
        return args.reduceRight(function (prev, next) {
            return next(prev);
        }, initial);
    }
}

Rather than take up time/space with a call to reverse on the array, it's simpler and easier to make sense of a reduceRight call.

An example of using this compose function would looks something like:

var square = function (input) {
    return input * input;
};

var add5 = function (input) {
    return input + 5;
};

var log = function (input) {
    console.log(input);
};

var result = compose(log, square, add5)(1); // -> 36

I'm certain there are many more technical examples of reduceRight being useful and this is just one.

Jamie Dixon
  • 53,019
  • 19
  • 125
  • 162
  • Indeed, function composition reads from right-to-left and hence it makes sense to use `reduceRight` in that case. However, you can just as easily make function composition read from left-to-right and use `reduce` instead. I guess that it's just a matter of preference. Personally, if I were to implement the `compose` function then I would use neither `reduce` nor `reduceRight` for performance reasons. I wouldn't want [twice the number of function call overheads](http://jsperf.com/compose-fold-vs-direct) for a composite function. As you said, there may be better examples. Hence I'll wait for it. – Aadit M Shah Feb 20 '15 at 11:51
  • 2
    @[Jamie Dixon] you're correct, it's a convenience utility which acts in proxy of Array.reverse – Elise Chant Feb 22 '15 at 20:08
4

You are absolutely correct, to the point where I'm not entirely sure this is even a real question. Laziness and fusion are both huge reasons why foldr is preferred in Haskell. Both of these things are absent in JavaScript's arrays, so there is literally no reason to use reduceRight in real world JavaScript. I mean, you could contrive a case where you've constructed an array by pushing things onto the end, and then you want to iterate over them from newest to oldest while accumulating a result. But imo that's very contrived.


Just to illustrate the Haskell side of things. Note that in Haskell, a right fold does not actually perform evaluation from right to left. You can think about grouping evaluation from right to left, but due to laziness, that's not what is computed. Consider:

foldr (\a _ -> Just a) undefined [1..]

I've supplied an undefined starting value for the accumulator, and the infinite list of natural numbers to fold over. Oh dear. Yet this matters not one bit. This expression happily evaluates to Just 1.

Conceptually, the grouping works like this:

let step a _ = Just a
let foldTheRest = foldr step undefined [2..]
step 1 foldTheRest

Thinking about the grouping, we "fold the rest", then we apply the step function to two arguments: the "first element of the list", and "whatever we got from folding the rest of the list." However, since the stepping function doesn't even need it's accumulator argument, that part of the computation is never evaluated. All we needed to evaluate this particular fold was the "first element of the list."


To reiterate, JavaScript arrays retain none of the benefits of foldr that Haskell enjoys, so there is literally no reason to use reduceRight. (In contrast, there are sometimes good reasons in Haskell to use a strict left fold.)

n.b. I disagree with your other question where you conclude that "the native implementation of reduceRight is wrong." I agree that it's annoying that they have chosen the argument order that they did, but it's not inherently wrong.

Dan Burton
  • 53,238
  • 27
  • 117
  • 198
2

Use reduceRight when you are trying to create a new array of tail items from a given array:

var arr = [1,2,2,32,23,4,5,66,22,35,78,8,9,9,4,21,1,1,3,4,4,64,46,46,46,4,6,467,3,67];

function tailItems(num) {
    var num = num + 1 || 1;
    return arr.reduceRight(function(accumulator, curr, idx, arr) {
        if (idx > arr.length - num) {
            accumulator.push(curr);
        }
        return accumulator;
    }, []);
}

console.log(tailItems(5));

//=> [67, 3, 467, 6, 4]

Another interesting thing that is useful about reduceRight is that since it reverses the array which it is performing on, the index parameter of the projection function provides array indexes starting from arr.length, such as:

var arr = [1,2,2,32,23];

arr.reduceRight(function(accumulator, curr, idx, arr) {
    console.log(idx);
}, '');

// => 4,3,2,1,0

This could be useful if you are trying to find a particular array item by its index ie arr[idx] that is toward the tail of an array, which obviously becomes more practical the larger the array - think thousands of items.

Perhaps a good in the wild example would be a social media feed which shows the latest 10 items at first and may load more on demand. Consider how reduceRight might be practical in this instance to organise collections of arrays in reverse order to suit this example.

Elise Chant
  • 5,048
  • 3
  • 29
  • 36
  • Nice examples, Elise. – Jamie Dixon Feb 22 '15 at 22:05
  • 1
    These are both bad examples, since they're both abusing the fold operation: in the first, you're changing a single `accumulator` value, and in the second you're printing stuff. That means that neither case actually does a *fold*, they're just iterations. – Eli Barzilay Feb 16 '16 at 20:44
1

Additionally you can utilize reduceRight, if you need to construct a nested computation/data structure inside out. Instead of manually writing

const compk = f => g => x => k => f(x) (x => g(x) (k));

const compk3 = (f, g, h) => x => k => f(x) (x => g(x) (y => h(y) (k)));

const inck = x => k => setTimeout(k, 0, x + 1);

const log = prefix => x => console.log(prefix, x);

compk3(inck, inck, inck) (0) (log("manually")); // 3

I want to apply a programmatic solution that builds the recursive structure:

const compkn = (...fs) => k => fs.reduceRight((chain, f) => x => f(x) (chain), k);

const inc = x => x + 1;

const lift = f => x => k => k(f(x));

const inck = x => k => setTimeout(k, 0, x + 1);

const log = prefix => x => console.log(prefix, x);

compkn(inck, lift(inc), inck) (log("programmatically")) (0); // 0
1

Array.reduceRight() is great when:

  • you need to iterate over an Array of items to create HTML
  • AND need a counter in HTML prior to the items

.

var bands = {
    Beatles: [
        {name: "John", instruments: "Guitar"},
        {name: "Paul", instruments: "Guitar"},
        {name: "George", instruments: "Guitar"},
        {name: "Ringo", instruments: "Drums"}]
};
function listBandplayers(bandname, instrument) {
    var bandmembers = bands[bandname];
    var arr = [  "<B>" , 0 , ` of ${bandmembers.length} ${bandname} play ` , instrument , "</B>",
                "\n<UL>" , ...bandmembers , "\n</UL>" ];
    var countidx = 1;
    return arr.reduceRight((html, item, idx, _array) => {
            if (typeof item === 'object') {
                if (item.instruments.contains(instrument)) _array[countidx]++;
                item = `\n\t<LI data-instruments="${item.instruments}">` + item.name + "</LI>";
            }
            return item + html;
    });
}
console.log( listBandplayers('Beatles', 'Drums') );
/*
<B>1 of 4 Beatles play Drums</B>
<UL>
    <LI data-instruments="Guitar">John</LI>
    <LI data-instruments="Guitar">Paul</LI>
    <LI data-instruments="Guitar">George</LI>
    <LI data-instruments="Drums">Ringo</LI>
</UL>
*/
console.log( listBandplayers('Beatles', 'Guitar') );
/*
<B>3 of 4 Beatles play Guitar</B>
<UL>
    <LI data-instruments="Guitar">John</LI>
    <LI data-instruments="Guitar">Paul</LI>
    <LI data-instruments="Guitar">George</LI>
    <LI data-instruments="Drums">Ringo</LI>
</UL>
*/
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
0

Array.prototype.reduceRight is actually useful in Javascript even with strict evaluation. In order to see why let's look at the map transducer in Javascript and a trivial example. Since I am a functional programmer my version mainly relies on curried functions:

const mapper = f => g => x => y => g(x) (f(y));

const foldl = f => acc => xs => xs.reduce((acc, x) => f(acc) (x), acc);

const add = x => y => x + y;

const get = k => o => o[k];

const len = get("length");

const concatLen = foldl(mapper(len) (add)) (0);

const xss = [[1], [1,2], [1,2,3]];

console.log(concatLen(xss)); // 6

mapper (f => g => x => y => g(x) (f(y))) is essentially function composition in the second argument. And when we recall that a right fold ((a -> b -> b) -> b -> [a] -> b) has flipped arguments inside the reducer, we can replace mapper with normal function composition. Due to abstraction over arity we also can compose a binary function with an unary one easily.

Unfortunately, the argument order of Array.prototype.reducdeRight is broken, because it expects the accumulator as the first argument of the reducer. So we need to fix that with a weird looking lambda:

const foldr = f => acc => xs => xs.reduceRight((acc, x) => f(x) (acc), acc);

const comp = (f, g) => x => f(g(x));

const add = x => y => x + y;

const len = xs => xs.length;

const concatLen = foldr(comp(add, len)) (0);

const xss = [[1], [1,2], [1,2,3]];

console.log(concatLen(xss));

I think this is a significant improvement since we lower the mental burden to actually understand the code (comp is more common than mapper) and are even more DRY.