在 JavaScript 中原生实现 reduceRight 是错误的

Native implementation of reduceRight in JavaScript is wrong

本文关键字:错误 reduceRight 实现 JavaScript 原生      更新时间:2023-09-26

对于数组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)));

我们需要翻转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 。这样,即使对于非交换结合运算,上述关系也成立。

所以,我的问题是:

    按照
  1. 我的方式实施reduceRight不是更有意义吗?
  2. 为什么本机reduceRight没有像我那样实现?
按照

我的方式实施reduceRight不是更有意义吗?

或。但是,JavaScript 数组迭代器并非来自纯函数式编程背景。

为什么本机reduceRight没有像我那样实现?

因为具有相同的参数顺序更简单(更容易记住(,所以累加器始终排在第一位。

数组的原始操作是 reduce ,它一如既往地从 0 迭代到 n-1。只有在 Haskell 及其递归构建的列表中foldr更有意义(具有build对偶性,在无限列表上懒惰地工作......请注意命名不是reduce + reduceLeft ...

然后reduceRight不会反转折叠操作,它只是反转迭代顺序。这也是文档和教程中通常如何解释的,例如在权威指南中:

reduceRight()的工作方式与reduce()一样,只是它从最高处处理数组。

此外,在 Mozilla 的 JS 1.8 数组附加功能reduce/reduceRight(参见错误 363040(的第一个实现遵循了这种方法:它只是翻转了 end 的开头并否定了步长值。

戴夫·赫尔曼(Dave Herman(对ES4规范的注释遵循了这一思路。它确实提到了 Haskell,但整个文档根本没有处理callback的参数顺序。也许在 Haskells 不常见的语法或规范类型名称中丢失了不同的顺序,因此两个签名都以 (a -> b -> … 开头。对缺少的thisObject参数进行了更多讨论。

一些相关摘录:

[该方法]的好处:

  • 就像 Python => Python 社区的思想份额
  • 折叠的完全通用性(左(
  • 但也要做一个简单的情况,其中第一个元素是基础 元素,更简单

我猜大多数人会发现从左到右版本的减少更多
直观,因为它们通常从左到右迭代数组。 另外,这就是Python所做的。

我认为提供一个reduceRight也很重要,
因为不是每个操作都是关联的,有时人们需要 从右到左。

最后,这就是进入 EcmaScript 规范的内容:

阵列附加功能 :按照FF当前支持的方式进行规范