【发布时间】:2018-09-11 10:59:04
【问题描述】:
让我们从定义开始:transducer 是一个接受 reducer 函数并返回 reducer 函数的函数。
reducer 是一个二进制函数,它接受一个累加器和一个值并返回一个累加器。可以使用 reduce 函数执行 reducer(注意:所有函数都是 curried 但我已经列出了这个以及 pipe 和 compose 的定义,以便于阅读 - 你可以在 @987654321 中看到它们@):
const reduce = (reducer, init, data) => {
let result = init;
for (const item of data) {
result = reducer(result, item);
}
return result;
}
使用reduce,我们可以实现map 和filter 函数:
const mapReducer = xf => (acc, item) => [...acc, xf(item)];
const map = (xf, arr) => reduce(mapReducer(xf), [], arr);
const filterReducer = predicate => (acc, item) => predicate(item) ?
[...acc, item] :
acc;
const filter = (predicate, arr) => reduce(filterReducer(predicate), [], arr);
我们可以看到map 和filter 之间有一些相似之处,并且这两个函数都只适用于数组。另一个缺点是,当我们组合这两个函数时,每一步都会创建一个临时数组,该数组会被传递给另一个函数。
const even = n => n % 2 === 0;
const double = n => n * 2;
const doubleEven = pipe(filter(even), map(double));
doubleEven([1,2,3,4,5]);
// first we get [2, 4] from filter
// then final result: [4, 8]
换能器帮助我们解决了这个问题:当我们使用换能器时,不会创建临时数组,我们可以泛化我们的函数,使其不仅适用于数组。 Transducers 需要 Transducers 通常通过传递给transduce 函数才能工作transduce 函数来执行:
const transduce = (xform, iterator, init, data) =>
reduce(xform(iterator), init, data);
const mapping = (xf, reducer) => (acc, item) => reducer(acc, xf(item));
const filtering = (predicate, reducer) => (acc, item) => predicate(item) ?
reducer(acc, item) :
acc;
const arrReducer = (acc, item) => [...acc, item];
const transformer = compose(filtering(even), mapping(double));
const performantDoubleEven = transduce(transformer, arrReducer, [])
performantDoubleEven([1, 2, 3, 4, 5]); // -> [4, 8] with no temporary arrays created
我们甚至可以使用transducer 定义数组map 和filter,因为它是如此可组合:
const map = (xf, data) => transduce(mapping(xf), arrReducer, [], data);
const filter = (predicate, data) => transduce(filtering(predicate), arrReducer, [], data);
如果您想运行代码,可以使用实时版本 -> https://runkit.com/marzelin/transducers
我的推理有道理吗?
【问题讨论】:
-
我没有看你的代码,但是描述是正确的,输出看起来是正确的。请记住,有两种方法可以对列表执行一系列转换:您可以将第一个转换应用于每个元素,然后应用第二个等。或者您可以组合转换,将组合转换应用于第一个元素,然后第二,等等。传感器是第二件事。它们甚至比这更酷,它们没有说明集合的性质,所以你可以在 Observables、生成器等上使用它们。
-
我在 JavaScript 中 implemented transducers 玩了一会儿,如果你愿意,可以查看 repo 并比较笔记。
-
这很好。在此之前不知道传感器是一回事。
-
@rmn 它们是为 Clojure 语言开发的,但您几乎可以用任何具有高阶函数的语言来实现它们。见this for details
-
是的,您的理解似乎很好,符合普遍的期望。你从哪里学来的?请注意,对于 js,还有一个 interoperability protocol。
标签: javascript functional-programming transducer