使用 ramda 实现自定义减速器

Implementing a custom reducer using ramda

我知道 Ramda.js 提供了一个 reduce 函数,但我正在尝试学习如何使用 ramda,我认为 reducer 是一个很好的例子。给定以下代码,什么是更有效和更实用的方法?

(function(){

  // Some operators. Sum and multiplication.
  const sum = (a, b) => a + b;
  const mult = (a, b) => a * b;

  // The reduce function
  const reduce = R.curry((fn, accum, list) => {
    const op = R.curry(fn);
    while(list.length > 0){
      accum = pipe(R.head, op(accum))(list);
      list = R.drop(1, list);
    }
    return accum;
  });

  const reduceBySum = reduce(sum, 0);
  const reduceByMult = reduce(mult, 1);

  const data = [1, 2, 3, 4];
  const result1 = reduceBySum(data);
  const result2 = reduceByMult(data);

  console.log(result1); // 1 + 2 + 3 + 4 => 10
  console.log(result2); // 1 * 2 * 3 * 4 => 24

})();

运行 在 REPL 上:http://ramdajs.com/repl/

我假设这是一个学习练习,而不是用于实际应用。正确吗?

您肯定可以通过该代码获得一些效率。在 Ramda 实现的核心,当所有的调度、转换等都被剥离时,是这样的:

const reduce = curry(function _reduce(fn, acc, list) {
  var idx = 0;
  while (idx < list.length) {
    acc = fn(acc, list[idx]);
    idx += 1;
  }
  return acc;
});

我还没有测试过,但这可能会在您的版本上有所收获,因为它只使用严格需要的函数调用数量:一个用于列表的每个成员,并且它通过简单的迭代来实现。您的版本添加了对 curry 的调用,然后,在每次迭代中,对 pipehead 的调用,对柯里化 op 函数的调用,对 pipe 调用,并到 drop。所以这个应该更快。

另一方面,这段代码是命令式的。如果您想使用更纯粹的功能,则需要使用递归解决方案。这是一个版本:

const reduce = curry(function _reduce(fn, acc, list) {
  return (list.length) ? _reduce(fn, fn(acc, head(list)), tail(list)) : acc;
});

这牺牲了上述调用 tail 的所有性能。但它显然更像是一个直接的功能实现。然而,在许多现代 JS 引擎中,由于堆栈深度,这甚至无法处理更大的列表。

因为它是尾递归的,所以它可以利用 ES2015 指定的尾调用优化,但目前还没有实现。在那之前,它主要是出于学术兴趣。即使它可用,因为 head 和 - 特别是 - tail 在那里调用,它会比上面的命令式实现慢得多。

您可能有兴趣知道 Ramda 是对生成的 API 的第二次尝试。它的原始作者(免责声明:我是其中之一)首先在后一个版本的基础上构建了 Eweda。正是由于这些原因,该实验失败了。 Javascript 根本无法处理这种递归...但是。