对于在f
数组元素上的关联操作a
,以下关系应成立:a.reduce(f)
应等效于a.reduceRight(f)
。
确实,它对于关联和可交换的运算都适用。对于
例:
const a = [0,1,2,3,4,5,6,7,8,9];
const add = (a, b) => a + b;
console.log(a.reduce(add));
console.log(a.reduceRight(add));
但是,对于关联但非可交换的操作,它并不成立。例如:
const a = [[0,1],[2,3],[4,5],[6,7],[8,9]];
const concat = (a, b) => a.concat(b);
console.log(JSON.stringify(a.reduce(concat)));
console.log(JSON.stringify(a.reduceRight(concat)));
我们需要将
f
的参数翻转为reduceRight
以使其等效:const a = [[0,1],[2,3],[4,5],[6,7],[8,9]];
const concat = (a, b) => a.concat(b);
const concatRight = (b, a) => a.concat(b);
console.log(JSON.stringify(a.reduce(concat)));
console.log(JSON.stringify(a.reduceRight(concatRight)));
这使我相信
reduceRight
的本机实现是错误的。我相信
reduceRight
函数应按以下方式实现:var REDUCE_ERROR = "Reduce of empty array with no initial value";
Array.prototype.reduceRight = function (f, acc) {
let { length } = this;
const noAcc = arguments.length < 2;
if (noAcc && length === 0) throw new TypeError(REDUCE_ERROR);
let result = noAcc ? this[--length] : acc;
while (length > 0) result = f(this[--length], result, length, this);
return result;
};
由于result
表示先前的值(右侧值),因此使其成为f
函数的第二个参数是有意义的。当前值表示左侧值。因此,使当前值成为函数f
的第一个参数是有意义的。这样,即使对于非可交换的关联运算,上述关系也成立。因此,我的问题是:
reduceRight
确实更有意义吗? reduceRight
没有像我那样实现? 最佳答案
也许。但是,JavaScript数组迭代器并非来自纯函数式编程背景。
因为具有相同的参数顺序更容易(更容易记住),所以累加器始终是第一个。
数组上的原始操作是reduce
,它一如既往地从0迭代到n-1。只有在具有递归构建列表的Haskell中,foldr
才有意义(具有build
对偶性,可以在无限列表上懒惰工作)。注意命名不是reduce
+ reduceLeft
…
然后reduceRight
不会反转折叠操作,它只是反转迭代顺序。这也是文档和教程(例如《权威指南》)中通常解释的方式:
同样,对于JS 1.8,first implementation中reduce
/ reduceRight
的Bug 363040(请参阅Mozilla's array extras)也遵循这种方法:它只是从end开始翻转而否定step值。
ES4规范的notes of Dave Herman遵循了这一思路。它确实提到了Haskell,但是整个文档根本不处理callback
的参数顺序。可能是因为Haskells不常见的语法或规范的类型名丢失了唯一顺序,所以两个签名都以(a -> b -> …
开头。 missing thisObject
parameter进行了更多讨论。
一些相关摘录:
最后,这就是got into the EcmaScript spec: