使用 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
的调用,然后,在每次迭代中,对 pipe
和 head
的调用,对柯里化 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 根本无法处理这种递归...但是。
我知道 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
的调用,然后,在每次迭代中,对 pipe
和 head
的调用,对柯里化 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 根本无法处理这种递归...但是。