在JavaScript中本地实现reduceRight是错误的

时间:2014-12-02 15:11:42

标签: javascript reduce fold associativity commutativity

对于数组f的元素的关联操作a,以下关系应该成立:a.reduce(f)应该等同于a.reduceRight(f)

实际上,它确实适用于相关和可交换的操作。对于 例如:

var a = [1,2,3,4,5,6,7,8,9,0];

alert(a.reduce(add) === a.reduceRight(add));

function add(a, b) {
    return a + b;
}

但是,对于关联但不可交换的操作,它不适用。例如:

var a = [[1,2],[3,4],[5,6],[7,8],[9,0]];

alert(equals(a.reduce(concat), a.reduceRight(concat)));

function concat(a, b) {
    return a.concat(b);
}

function equals(a, b) {
    var length = a.length;
    if (b.length !== length) return false;
    for (var i = 0; i < length; i++)
        if (a[i] !== b[i]) return false;
    return true;
}

我们需要为f翻转reduceRight的参数,使它们等效:

var a = [[1,2],[3,4],[5,6],[7,8],[9,0]];

alert(equals(a.reduce(concat), a.reduceRight(concatRight)));

function concat(a, b) {
    return a.concat(b);
}

function concatRight(b, a) {
    return a.concat(b);
}

function equals(a, b) {
    var length = a.length;
    if (b.length !== length) return false;
    for (var i = 0; i < length; i++)
        if (a[i] !== b[i]) return false;
    return true;
}

这让我相信reduceRight的原生实现是错误的。

我认为reduceRight函数应按如下方式实现:

var REDUCE_ERROR = "Reduce of empty array with no initial value";

Array.prototype.reduceRight = function (f, acc) {
    var a = this, length = a.length;

    if (arguments.length < 2) {
        if (length !== 0) var right = a[--length];
        else throw new TypeError(REDUCE_ERROR);
    } else var right = acc;

    while (length !== 0) right = f(a[--length], right, length, a);

    return right;
};

由于right表示先前的值(右侧值),因此将其作为函数f的第二个参数是有意义的。当前值表示左侧值。因此,将当前值作为函数f的第一个参数是有意义的。这样,即使对于非交换关联操作,上述关系也成立。

所以,我的问题是:

  1. reduceRight以我的方式实施是否确实更有意义?
  2. 为什么本地reduceRight没有像我那样实现?

1 个答案:

答案 0 :(得分:4)

  

reduceRight以我的方式实施是否确实更有意义?

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

  

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

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

数组上的基本操作是reduce,它一如既往地从0迭代到n-1。只有在Haskell中使用递归构建的列表foldr才更有意义(具有build二元性,在无限列表上懒得工作......)。请注意命名不是reduce + reduceLeft ...

然后reduceRight不会反转折叠操作,它只是反转 迭代顺序。这也是文档和教程中常见的解释,例如 The Definitive Guide

  

reduceRight()的工作原理与reduce()类似,不过它会从最高处理数组。

对于JS 1.8,first implementationreduce / reduceRight Bug 363040 Mozilla's array extrasnotes of Dave Herman也采用了这种方法:它只是以“结束”开头否定了步骤值。

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

一些相关的摘录:

  

[方法]的好处:

     
      
  • 就像Python =&gt; Python社区mindshare
  •   
  • 折叠的完整普遍性(左)
  •   
  • 但也是简单的情况,其中第一个元素是基础   元素,更简单
  •   
     

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

     

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

最后,这就是{{3}}:

  

数组附加内容 :按照FF当前支持的方式进行指定