前段时间,我在StackOverflow上发布了一个问题,显示native implementation of reduceRight
in JavaScript is annoying。因此,我创建了一个Haskell风格的foldr
函数作为补救措施:
function foldr(array, callback, initial) {
var length = array.length;
if (arguments.length < 3) {
if (length > 0) var result = array[--length];
else throw new Error("Reduce of empty array with no initial value");
} else var result = initial;
while (length > 0) {
var index = --length;
result = callback(array[index], result, index, array);
}
return result;
}
但是,我从未使用过这个foldr
函数,因为我从不需要从右到左遍历数组。这让我思考,为什么我不像在Haskell中那样在JavaScript中使用foldr
以及在JavaScript中使用foldr
的一些真实世界示例?
我可能错了,但我相信foldr
函数在Haskell中被广泛使用,因为:
foldr
/ build
(Correctness of short cut fusion: foldr
/build
)这可以解释为什么foldr
或reduceRight
未在JavaScript中广泛使用。我还没有看到foldr
仅在从右到左的迭代顺序中使用reduceRight
。
这让我想到了两个问题:
reduceRight
的一些真实世界示例是什么?也许您已经在npm包中使用过它。如果您可以将我链接到您的代码并解释为什么需要使用reduce
代替reduceRight
,那就太棒了。reduce
没有像foldr
那样广泛使用?我已就此事提供了两分钱。我认为reduceRight
主要仅用于其懒惰,这就是为什么reduceRight
在JavaScript中不是很有用的原因。但是,我可能错了。对于第一个问题,我试图找到一些在JavaScript中使用reduceRight
的真实示例。但是,我没有找到任何令人满意的答案。我发现的唯一例子是微不足道的和理论上的:
when to use reduce and reduceRight?
我正在寻找的是一个实际的例子。何时在JavaScript中使用reduce
代替{{1}}?
对于第二个问题,我理解这主要是基于意见的,这就是为什么如果你不回答它就好了。这篇文章的主要焦点是第一个问题,而不是第二个问题。
答案 0 :(得分:7)
要回答您的第一个问题,当您想要以从左到右的方式指定项目但以从右到左的方式执行时,reduceRight
非常方便。
考虑一下这个天然的compose函数实现,它从左到右接受参数,但是从右到左读取和执行:
var compose = function () {
var args = [].slice.call(arguments);
return function (initial) {
return args.reduceRight(function (prev, next) {
return next(prev);
}, initial);
}
}
不是通过调用数组上的reverse
占用时间/空间,而是更简单,更容易理解reduceRight
调用。
使用此compose
函数的示例如下所示:
var square = function (input) {
return input * input;
};
var add5 = function (input) {
return input + 5;
};
var log = function (input) {
console.log(input);
};
var result = compose(log, square, add5)(1); // -> 36
我确信还有更多reduceRight
有用的技术示例,这只是一个。
答案 1 :(得分:4)
你是绝对正确的,我不完全确定这甚至是一个真正的问题。懒惰和融合都是巨大的原因,为什么在Haskell中首选foldr
。这两个东西在JavaScript的数组中都不存在,所以实际上没有理由在现实世界的JavaScript中使用reduceRight。我的意思是,你可以设计一个案例,你通过将事物推到最后来构建一个数组,然后你想在累积结果的同时从最新到最旧迭代它们。但是imo非常做作。
只是为了说明Haskell方面的事情。请注意,在Haskell中,右侧折叠不从右到左实际执行评估。您可以认为关于分组评估从右到左,但由于懒惰,这不是计算。考虑:
foldr (\a _ -> Just a) undefined [1..]
我为累加器提供了undefined
起始值,以及要折叠的无限自然数列表。噢亲爱的。然而,这不重要。这个表达式很高兴地评估为Just 1
。
从概念上讲,分组的工作原理如下:
let step a _ = Just a
let foldTheRest = foldr step undefined [2..]
step 1 foldTheRest
考虑分组,我们“折叠其余部分”,然后我们将step
函数应用于两个参数:“列表的第一个元素”,以及“我们从折叠列表的其余部分获得的任何内容” “。但是,由于步进函数甚至不需要它的累加器参数,因此永远不会计算该部分计算。我们需要评估这个特定的折叠是“列表的第一个元素。”
重申一下,JavaScript数组保留 none Haskell所享有的foldr
的好处,因此实际上没有理由使用reduceRight
。 (相比之下,Haskell有时候有充分的理由使用严格的左侧折叠。)
n.b。我不同意你的另一个问题,你得出的结论是“reduceRight的本地实现是错误的”。我同意讨厌他们选择了他们所做的参数顺序,但它本身并不是错误的。
答案 2 :(得分:2)
当您尝试从给定数组创建新的尾部项数组时使用reduceRight
:
var arr = [1,2,2,32,23,4,5,66,22,35,78,8,9,9,4,21,1,1,3,4,4,64,46,46,46,4,6,467,3,67];
function tailItems(num) {
var num = num + 1 || 1;
return arr.reduceRight(function(accumulator, curr, idx, arr) {
if (idx > arr.length - num) {
accumulator.push(curr);
}
return accumulator;
}, []);
}
console.log(tailItems(5));
//=> [67, 3, 467, 6, 4]
另一个对reduceRight有用的有趣的事情是,因为它反转它正在执行的数组,所以投影函数的index参数提供从arr.length开始的数组索引,例如:
var arr = [1,2,2,32,23];
arr.reduceRight(function(accumulator, curr, idx, arr) {
console.log(idx);
}, '');
// => 4,3,2,1,0
如果你试图通过它的索引找到一个特定的数组项,例如朝向数组尾部的arr[idx]
,这可能很有用,当数组越大时,这显然变得更实用 - 想想成千上万的项目。
在野外示例中,一个好的例子可能是社交媒体源,它首先显示最新的10个项目,并可能根据需要加载更多。考虑reduceRight
在这个实例中如何以相反的顺序组织数组的集合以适应这个例子。
答案 3 :(得分:1)
此外,如果需要在内部构建嵌套计算/数据结构,则可以使用reduceRight
。而不是手动编写
const compk = f => g => x => k => f(x) (x => g(x) (k));
const compk3 = (f, g, h) => x => k => f(x) (x => g(x) (y => h(y) (k)));
const inck = x => k => setTimeout(k, 0, x + 1);
const log = prefix => x => console.log(prefix, x);
compk3(inck, inck, inck) (0) (log("manually")); // 3
&#13;
我想应用构建递归结构的编程解决方案:
const compkn = (...fs) => k => fs.reduceRight((chain, f) => x => f(x) (chain), k);
const inc = x => x + 1;
const lift = f => x => k => k(f(x));
const inck = x => k => setTimeout(k, 0, x + 1);
const log = prefix => x => console.log(prefix, x);
compkn(inck, lift(inc), inck) (log("programmatically")) (0); // 0
&#13;
答案 4 :(得分:0)
Array.reduceRight()
在以下情况下非常棒:
var bands = {
Beatles: [
{name: "John", instruments: "Guitar"},
{name: "Paul", instruments: "Guitar"},
{name: "George", instruments: "Guitar"},
{name: "Ringo", instruments: "Drums"}]
};
function listBandplayers(bandname, instrument) {
var bandmembers = bands[bandname];
var arr = [ "<B>" , 0 , ` of ${bandmembers.length} ${bandname} play ` , instrument , "</B>",
"\n<UL>" , ...bandmembers , "\n</UL>" ];
var countidx = 1;
return arr.reduceRight((html, item, idx, _array) => {
if (typeof item === 'object') {
if (item.instruments.contains(instrument)) _array[countidx]++;
item = `\n\t<LI data-instruments="${item.instruments}">` + item.name + "</LI>";
}
return item + html;
});
}
console.log( listBandplayers('Beatles', 'Drums') );
/*
<B>1 of 4 Beatles play Drums</B>
<UL>
<LI data-instruments="Guitar">John</LI>
<LI data-instruments="Guitar">Paul</LI>
<LI data-instruments="Guitar">George</LI>
<LI data-instruments="Drums">Ringo</LI>
</UL>
*/
console.log( listBandplayers('Beatles', 'Guitar') );
/*
<B>3 of 4 Beatles play Guitar</B>
<UL>
<LI data-instruments="Guitar">John</LI>
<LI data-instruments="Guitar">Paul</LI>
<LI data-instruments="Guitar">George</LI>
<LI data-instruments="Drums">Ringo</LI>
</UL>
*/
答案 5 :(得分:0)
Array.prototype.reduceRight
在Javascript中也很有用。为了了解为什么让我们看一下Javascript中的地图传感器和一个简单的例子。由于我是一名函数式程序员,我的版本主要依赖于curried函数:
const mapper = f => g => x => y => g(x) (f(y));
const foldl = f => acc => xs => xs.reduce((acc, x) => f(acc) (x), acc);
const add = x => y => x + y;
const get = k => o => o[k];
const len = get("length");
const concatLen = foldl(mapper(len) (add)) (0);
const xss = [[1], [1,2], [1,2,3]];
console.log(concatLen(xss)); // 6
&#13;
mapper
(f => g => x => y => g(x) (f(y))
)本质上是第二个参数中的函数组合。当我们回想起右折((a -> b -> b) -> b -> [a] -> b
)在reducer中翻转了参数时,我们可以用正常的函数组合替换mapper
。由于对arity的抽象,我们也可以很容易地用一元函数组成二元函数。
不幸的是,Array.prototype.reducdeRight
的参数顺序被打破了,因为它期望累加器作为reducer的第一个参数。所以我们需要用奇怪的lambda来解决这个问题:
const foldr = f => acc => xs => xs.reduceRight((acc, x) => f(x) (acc), acc);
const comp = (f, g) => x => f(g(x));
const add = x => y => x + y;
const len = xs => xs.length;
const concatLen = foldr(comp(add, len)) (0);
const xss = [[1], [1,2], [1,2,3]];
console.log(concatLen(xss));
&#13;
我认为这是一项重大改进,因为我们降低了实际理解代码的心理负担(comp
比mapper
更常见)并且更加干燥。