Ale*_*lls 4 javascript functional-programming node.js
我有这种简单的情况,我想过滤并映射到相同的值,如下所示:
const files = results.filter(function(r){
return r.file;
})
.map(function(r){
return r.file;
});
Run Code Online (Sandbox Code Playgroud)
为了节省代码行,以及提高性能,我正在寻找:
const files = results.filterAndMap(function(r){
return r.file;
});
Run Code Online (Sandbox Code Playgroud)
这是否存在,或者我应该自己写点什么?我想在几个地方使用这样的功能,从来没有费心去研究它.
传感器
在最通用的形式中,您的问题的答案在于传感器.但是,我们走得太抽象之前,让我们先看看一些基础知识-下面,我们实行一对夫妇换能器mapReduce,filterReduce以及tapReduce; 你可以添加你需要的任何其他.
const mapReduce = map => reduce =>
(acc, x) => reduce (acc, map (x))
const filterReduce = filter => reduce =>
(acc, x) => filter (x) ? reduce (acc, x) : acc
const tapReduce = tap => reduce =>
(acc, x) => (tap (x), reduce (acc, x))
const tcomp = (f,g) =>
k => f (g (k))
const concat = (xs,ys) =>
xs.concat(ys)
const transduce = (...ts) => xs =>
xs.reduce (ts.reduce (tcomp, k => k) (concat), [])
const main =
transduce (
tapReduce (x => console.log('with:', x)),
filterReduce (x => x.file),
tapReduce (x => console.log('has file:', x.file)),
mapReduce (x => x.file),
tapReduce (x => console.log('final:', x)))
const data =
[{file: 1}, {file: undefined}, {}, {file: 2}]
console.log (main (data))
// with: { file: 1 }
// has file: 1
// final: 1
// with: { file: undefined }
// with: {}
// with: { file: 2 }
// has file: 2
// final: 2
// => [ 1, 2 ]Run Code Online (Sandbox Code Playgroud)
可连接的API
也许您对代码的简单性感到满意,但您对某些非传统的API不满意.如果你想保留的能力,以连锁.map,.filter,.whatever电话不增加不必要的迭代,我们可以做一个通用接口,用以转换,使我们可链接的API最重要的是-这个答案是从我上面分享的链接和适应其他答案我有关于传感器
// Trans Monoid
const Trans = f => ({
runTrans: f,
concat: ({runTrans: g}) =>
Trans (k => f (g (k)))
})
Trans.empty = () =>
Trans(k => k)
// transducer "primitives"
const mapper = f =>
Trans (k => (acc, x) => k (acc, f (x)))
const filterer = f =>
Trans (k => (acc, x) => f (x) ? k (acc, x) : acc)
const tapper = f =>
Trans (k => (acc, x) => (f (x), k (acc, x)))
// chainable API
const Transduce = (t = Trans.empty()) => ({
map: f =>
Transduce (t.concat (mapper (f))),
filter: f =>
Transduce (t.concat (filterer (f))),
tap: f =>
Transduce (t.concat (tapper (f))),
run: xs =>
xs.reduce (t.runTrans ((xs,ys) => xs.concat(ys)), [])
})
// demo
const main = data =>
Transduce()
.tap (x => console.log('with:', x))
.filter (x => x.file)
.tap (x => console.log('has file:', x.file))
.map (x => x.file)
.tap (x => console.log('final:', x))
.run (data)
const data =
[{file: 1}, {file: undefined}, {}, {file: 2}]
console.log (main (data))
// with: { file: 1 }
// has file: 1
// final: 1
// with: { file: undefined }
// with: {}
// with: { file: 2 }
// has file: 2
// final: 2
// => [ 1, 2 ]Run Code Online (Sandbox Code Playgroud)
可连接的API,需要2
作为一个练习来实现用尽可能少的依赖仪式尽可能链接API,我重写了代码片段,而不依赖于Trans幺实施或原始传感器mapper,filterer等等-感谢您的评论@ftor.
就整体可读性而言,这是一个明确的降级.我们失去了只看它并理解发生了什么的能力.我们也失去了monoid接口,这使我们很容易在其他表达式中推理我们的传感器.这里的一大收获Transduce是包含在10行源代码中的定义; 与之前的28个相比 - 所以虽然表达式更复杂,但在大脑开始挣扎之前,你可能已经完成了整个定义的阅读
// chainable API only (no external dependencies)
const Transduce = (t = k => k) => ({
map: f =>
Transduce (k => t ((acc, x) => k (acc, f (x)))),
filter: f =>
Transduce (k => t ((acc, x) => f (x) ? k (acc, x) : acc)),
tap: f =>
Transduce (k => t ((acc, x) => (f (x), k (acc, x)))),
run: xs =>
xs.reduce (t ((xs,ys) => xs.concat(ys)), [])
})
// demo (this stays the same)
const main = data =>
Transduce()
.tap (x => console.log('with:', x))
.filter (x => x.file)
.tap (x => console.log('has file:', x.file))
.map (x => x.file)
.tap (x => console.log('final:', x))
.run (data)
const data =
[{file: 1}, {file: undefined}, {}, {file: 2}]
console.log (main (data))
// with: { file: 1 }
// has file: 1
// final: 1
// with: { file: undefined }
// with: {}
// with: { file: 2 }
// has file: 2
// final: 2
// => [ 1, 2 ]Run Code Online (Sandbox Code Playgroud)
>谈谈表现
在速度方面,没有任何功能变体能够击败静态for循环,它将所有程序语句组合在一个循环体中.然而,上述传感器确实有可能比一系列.map/ .filter/ .whatever调用更快,其中通过大数据集的多次迭代将是昂贵的.
编码风格和实施
传感器的本质在于mapReduce,这就是我选择首先引入它的原因.如果你能理解如何进行多次mapReduce调用并将它们排列在一起,你就会理解传感器.
当然,您可以通过多种方式实现传感器,但我发现Brian的方法最有用,因为它将传感器编码为幺半群 - 有一个monoid允许我们对它做出各种方便的假设.一旦我们转换了一个阵列(一种类型的幺半群),您可能想知道如何转换任何其他幺半群......在这种情况下,请阅读该文章!
如果你真的需要在1个函数中执行它,你需要reduce像这样使用
results.reduce(
// add the file name to accumulator if it exists
(acc, result) => result.file ? acc.concat([result.file]) : acc,
// pass empty array for initial accumulator value
[]
)
Run Code Online (Sandbox Code Playgroud)
如果你需要挤出更多的性能,您可以更改concat到push,回到原来的累加器阵列,以避免产生额外的阵列.
但是,最快的解决方案可能是一个很好的旧for循环,它避免了所有的函数调用和堆栈帧
files = []
for (var i = 0; i < results.length; i++) {
var file = results[i].file
if (file) files.push(file)
}
Run Code Online (Sandbox Code Playgroud)
但我认为filter/map方法更具表现力和可读性