在 JavaScript 中使用 reduceRight 的真实示例
Real world examples of using reduceRight in JavaScript
不久前,我 post 在 Whosebug 上提出了一个问题,显示 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
以及在 [=] 中使用 foldr
的一些真实示例72=]?
我可能是错的,但我相信 foldr
函数在 Haskell 中被广泛使用,因为:
- 惰性求值(foldl is tail recursive, so how come foldr runs faster than foldl?)
- 使用
foldr
/build
(Correctness of short cut fusion: foldr
/build
) 的捷径融合
这可以解释为什么 foldr
或 reduceRight
没有在 JavaScript 中广泛使用。我还没有看到 foldr
仅用于其从右到左的迭代顺序的真实世界使用。
这引出了我的两个问题:
- 有哪些在 JavaScript 中使用
reduceRight
的真实示例?也许您已经在 npm 包中使用过它。如果您可以 link 我查看您的代码并解释为什么您需要使用 reduceRight
而不是 reduce
. ,那就太好了
- 为什么
reduceRight
不像 reduce
在 JavaScript 中那样广泛使用?我已经在这件事上提供了我的两分钱。我相信 foldr
主要只是因为它的懒惰而被使用,这就是为什么 reduceRight
在 JavaScript 中不是很有用。但是,我可能是错的。
对于第一个问题,我试图在 JavaScript 中找到一些使用 reduceRight
的真实示例。但是,我没有找到满意的答案。我发现的唯一例子是琐碎的和理论上的:
when to use reduce and reduceRight?
我要找的是一个实际的例子。什么时候在 JavaScript 中使用 reduceRight
而不是 reduce
?
关于第二个问题,我知道它主要是基于意见,这就是为什么你不回答也没关系。 post 主要关注第一个问题,而不是第二个问题。
您完全正确,以至于我什至不能完全确定这是一个真正的问题。懒惰和融合都是 巨大的 原因 foldr
在 Haskell 中更受欢迎。 JavaScript 的数组中没有这两个东西,因此实际上没有理由在现实世界中使用 reduceRight JavaScript。我的意思是,您可以想出这样一种情况,即通过将事物推到末尾来构造一个数组,然后您希望在累积结果的同时从最新到最旧迭代它们。但我觉得这很做作。
只是为了说明事情的 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
考虑到分组,我们 "fold the rest",然后我们将 step
函数应用于两个参数:"first element of the list" 和 "whatever we got from folding the rest of the list." 但是,由于步进函数甚至不需要它的累加器参数,那部分计算永远不会被评估。我们需要评估这个特定的折叠是 "first element of the list."
重申一下,JavaScript 数组保留了 none Haskell 享有的 foldr
的好处,所以有字面上没有理由使用 reduceRight
。 (相比之下,Haskell 中有时有充分的理由使用严格的左折叠。)
n.b。我不同意你的另一个问题,你得出的结论是 "the native implementation of reduceRight is wrong." 我同意 烦人 他们选择了他们所做的论证顺序,但这并不是天生的 错.
要回答您的第一个问题,当您想以从左到右的方式指定项目但以从右到左的方式执行它们时,reduceRight
非常方便。
考虑这个 compose 函数的天真实现,它从左到右接受参数,但从右到左读取和执行:
var compose = function () {
var args = [].slice.call(arguments);
return function (initial) {
return args.reduceRight(function (prev, next) {
return next(prev);
}, initial);
}
}
与其调用数组上的 reverse
占用 time/space,更简单、更容易理解 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
有用的技术示例,这只是一个。
尝试从给定数组创建新的尾项数组时使用 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有用的有趣的事情是,由于它反转了它正在执行的数组,投影函数的索引参数提供了从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
在这种情况下如何以相反的顺序组织数组集合以适合此示例。
此外,如果您需要构建一个由内而外的嵌套 computation/data 结构,您还可以使用 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
我想应用构建递归结构的编程解决方案:
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
Array.reduceRight()
在以下情况下很棒:
- 您需要遍历项目数组才能创建 HTML
- 并且在 HTML 项目
之前需要一个计数器
.
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>
*/
Array.prototype.reduceRight
实际上在 Javascript 即使有严格的评估也是有用的。为了了解原因,让我们看一下 Javascript 中的地图传感器和一个简单的例子。由于我是一名函数式程序员,我的版本主要依赖柯里化函数:
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
mapper
(f => g => x => y => g(x) (f(y))
) 本质上是第二个参数中的函数组合。当我们回忆起右折叠 ((a -> b -> b) -> b -> [a] -> b
) 在 reducer 中翻转了参数时,我们可以用正常的函数组合替换 mapper
。由于对元数的抽象,我们还可以轻松地将二元函数与一元函数组合起来。
不幸的是,Array.prototype.reducdeRight
的参数顺序被破坏了,因为它期望累加器作为减速器的第一个参数。所以我们需要用一个看起来很奇怪的 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));
我认为这是一个显着的改进,因为我们降低了实际理解代码的精神负担(comp
比 mapper
更常见)并且更干。
不久前,我 post 在 Whosebug 上提出了一个问题,显示 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
以及在 [=] 中使用 foldr
的一些真实示例72=]?
我可能是错的,但我相信 foldr
函数在 Haskell 中被广泛使用,因为:
- 惰性求值(foldl is tail recursive, so how come foldr runs faster than foldl?)
- 使用
foldr
/build
(Correctness of short cut fusion:foldr
/build
) 的捷径融合
这可以解释为什么 foldr
或 reduceRight
没有在 JavaScript 中广泛使用。我还没有看到 foldr
仅用于其从右到左的迭代顺序的真实世界使用。
这引出了我的两个问题:
- 有哪些在 JavaScript 中使用
reduceRight
的真实示例?也许您已经在 npm 包中使用过它。如果您可以 link 我查看您的代码并解释为什么您需要使用reduceRight
而不是reduce
. ,那就太好了
- 为什么
reduceRight
不像reduce
在 JavaScript 中那样广泛使用?我已经在这件事上提供了我的两分钱。我相信foldr
主要只是因为它的懒惰而被使用,这就是为什么reduceRight
在 JavaScript 中不是很有用。但是,我可能是错的。
对于第一个问题,我试图在 JavaScript 中找到一些使用 reduceRight
的真实示例。但是,我没有找到满意的答案。我发现的唯一例子是琐碎的和理论上的:
when to use reduce and reduceRight?
我要找的是一个实际的例子。什么时候在 JavaScript 中使用 reduceRight
而不是 reduce
?
关于第二个问题,我知道它主要是基于意见,这就是为什么你不回答也没关系。 post 主要关注第一个问题,而不是第二个问题。
您完全正确,以至于我什至不能完全确定这是一个真正的问题。懒惰和融合都是 巨大的 原因 foldr
在 Haskell 中更受欢迎。 JavaScript 的数组中没有这两个东西,因此实际上没有理由在现实世界中使用 reduceRight JavaScript。我的意思是,您可以想出这样一种情况,即通过将事物推到末尾来构造一个数组,然后您希望在累积结果的同时从最新到最旧迭代它们。但我觉得这很做作。
只是为了说明事情的 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
考虑到分组,我们 "fold the rest",然后我们将 step
函数应用于两个参数:"first element of the list" 和 "whatever we got from folding the rest of the list." 但是,由于步进函数甚至不需要它的累加器参数,那部分计算永远不会被评估。我们需要评估这个特定的折叠是 "first element of the list."
重申一下,JavaScript 数组保留了 none Haskell 享有的 foldr
的好处,所以有字面上没有理由使用 reduceRight
。 (相比之下,Haskell 中有时有充分的理由使用严格的左折叠。)
n.b。我不同意你的另一个问题,你得出的结论是 "the native implementation of reduceRight is wrong." 我同意 烦人 他们选择了他们所做的论证顺序,但这并不是天生的 错.
要回答您的第一个问题,当您想以从左到右的方式指定项目但以从右到左的方式执行它们时,reduceRight
非常方便。
考虑这个 compose 函数的天真实现,它从左到右接受参数,但从右到左读取和执行:
var compose = function () {
var args = [].slice.call(arguments);
return function (initial) {
return args.reduceRight(function (prev, next) {
return next(prev);
}, initial);
}
}
与其调用数组上的 reverse
占用 time/space,更简单、更容易理解 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
有用的技术示例,这只是一个。
尝试从给定数组创建新的尾项数组时使用 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有用的有趣的事情是,由于它反转了它正在执行的数组,投影函数的索引参数提供了从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
在这种情况下如何以相反的顺序组织数组集合以适合此示例。
此外,如果您需要构建一个由内而外的嵌套 computation/data 结构,您还可以使用 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
我想应用构建递归结构的编程解决方案:
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
Array.reduceRight()
在以下情况下很棒:
- 您需要遍历项目数组才能创建 HTML
- 并且在 HTML 项目 之前需要一个计数器
.
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>
*/
Array.prototype.reduceRight
实际上在 Javascript 即使有严格的评估也是有用的。为了了解原因,让我们看一下 Javascript 中的地图传感器和一个简单的例子。由于我是一名函数式程序员,我的版本主要依赖柯里化函数:
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
mapper
(f => g => x => y => g(x) (f(y))
) 本质上是第二个参数中的函数组合。当我们回忆起右折叠 ((a -> b -> b) -> b -> [a] -> b
) 在 reducer 中翻转了参数时,我们可以用正常的函数组合替换 mapper
。由于对元数的抽象,我们还可以轻松地将二元函数与一元函数组合起来。
不幸的是,Array.prototype.reducdeRight
的参数顺序被破坏了,因为它期望累加器作为减速器的第一个参数。所以我们需要用一个看起来很奇怪的 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));
我认为这是一个显着的改进,因为我们降低了实际理解代码的精神负担(comp
比 mapper
更常见)并且更干。