javascript - JavaScript 中 reduceRight 的原生实现是错误的

标签 javascript reduce fold associativity commutativity

对于数组 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 的第一个参数是有意义的。这样,即使对于非交换结合运算,上述关系也成立。

所以,我的问题是:

  1. 按照我的方式实现 reduceRight 不是更有意义吗?
  2. 为什么原生 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 like reduce(), 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/

相关文章:

javascript - 使 Webpack 在索引以外的文件中渲染

r - 当我在另一台设备上打开我的 .R 文件时如何保持折叠函数折叠。工作室

functional-programming - SML 中带有逻辑运算符的 foldr/foldl

Scala 向量折叠语法 (/: and :\and/:\)

javascript - 使用JS或Jquery读取css比例值

javascript - 如何将输入框的数组名称添加到 HTML 中?

javascript - React 路由器和嵌套布局

javascript - 对连续的相似元素数组进行分组

Javascript reduce() 直到值之和 < 变量

Javascript:使用 reduce() 将频率转换为百分比?