【问题标题】:Native implementation of reduceRight in JavaScript is wrongJavaScript 中 reduceRight 的原生实现是错误的
【发布时间】:2014-12-02 15:11:41
【问题描述】:

对于数组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 没有按照我的方式实现?

【问题讨论】:

  • 这不只是因为foldr and foldl的操作方向不同吗?这就是两者兼而有之的意义,非交换操作必然会返回不同的结果。
  • @ssube 它们可能在不同的方向上操作,但对于关联操作它们应该返回相同的结果。例如:foldl1 (++) xs == foldr1 (++) xs 在 Haskell 中是 True

标签: javascript reduce fold associativity commutativity


【解决方案1】:

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

也许吧。然而,JavaScript 数组迭代器并非来自纯函数式编程背景。

为什么原生 reduceRight 没有按照我的方式实现?

因为具有相同的参数顺序更简单(更容易记住),所以累加器总是在前。

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

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

reduceRight() 的工作方式与reduce() 类似,只是它从最高处开始处理数组。

对于 JS 1.8,Mozilla's array extras 中的 reduce/reduceRightfirst implementation(请参阅 Bug 363040)也遵循了这种方法:它只是将 start 与 end 翻转并否定 step 值。

ES4 规范的notes of Dave Herman 遵循了这一思路。它确实提到了 Haskell,但整个文档根本没有处理 callback 的参数顺序。也许在 Haskells 不常见的语法或规范类型名称中丢失了不同的顺序,因此两个签名都以 (a -&gt; b -&gt; … 开头。更多讨论进入missing thisObject parameter

一些相关的摘录:

[该方法]的好处:

  • 就像 Python => Python 社区的思想分享
  • 折叠的全面概括(左)
  • 还要做一个简单的例子,第一个元素是基础 元素,更简单

我猜大多数人会找到从左到右的 reduce more
直观,因为它们通常从左到右遍历数组。 另外,这就是 Python 所做的。

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

最后,这就是got into the EcmaScript spec

Array extras:指定它当前在 FF 中支持的方式

【讨论】:

    猜你喜欢
    • 2019-11-16
    • 2015-04-20
    • 2012-02-24
    • 1970-01-01
    相关资源
    最近更新 更多