对于数组 a
的元素的关联操作 f
,以下关系应该成立: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)));
我们需要为 reduceRight
翻转 f
的参数,使它们等价:
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
没有像我那样实现?
最佳答案
Doesn't it indeed make more sense for
reduceRight
to be implemented the way I did?
也许吧。但是,JavaScript 数组迭代器 并非来自纯函数式编程背景。
Why is the native
reduceRight
not implemented the way I did?
因为具有相同的参数顺序更简单(更容易记住),所以累加器始终在前。
数组的原始操作是reduce
,它一如既往地从0 迭代到n-1。只有在具有递归构建列表的 Haskell 中 foldr
才更有意义(具有 build
双重性,在无限列表上惰性工作......)。注意命名不是reduce
+reduceLeft
...
然后 reduceRight
不会反转折叠操作,它只是反转 迭代 顺序。这也是文档和教程中通常解释的方式,例如在权威指南中:
reduceRight()
works just likereduce()
, except that it processes the array from highest.
还有 first implementation reduce
/reduceRight
(参见 Bug 363040 )在 Mozilla's array extras 中对于 JS 1.8 遵循这种方法:它只是翻转开始和结束并否定步长值。
notes of Dave Herman对于 ES4 规范,遵循了这一思路。它确实提到了 Haskell,但整个文档根本没有处理 callback
的参数顺序。也许 Haskell 的不常见语法或规范类型名称中丢失了独特的顺序,因此两个签名都以 (a -> b -> …
开头。更多讨论进入了 missing thisObject
parameter。
一些相关摘录:
The benefits [of the approach]:
- just like Python => Python community mindshare
- full generality of fold (left)
- but also make the simple case, where the first element is the basis element, simpler
I'd guess most people find the left-to-right version of reduce more
intuitive, since they usually iterate over arrays from left to right. Plus that's what Python does.I think it would also be important to provide a reduceRight as well,
since not every operation is associative, and sometimes people need to go from right to left.
最后,这就是 got into the EcmaScript spec :
Array extras: Spec it the way it is currently supported in FF
关于javascript - JavaScript 中 reduceRight 的原生实现是错误的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27252657/