## 什么是 Transducer

### reducers

reducer函数接收一个累积值（译者注：一个通过迭代后累积成的变量）和一个值，它将这个值加入到累积值中。比如，如果 `[1,2,3]` 是一个 `累积值``4` 是将要被添加到累积值的一个值， 执行 `(acc, val) => acc.concat([val])`通过这个reducer将返回`[1,2,3,4]`

``````const acc = [1, 2, 3];
const val = 4;
const reducer = (acc, val) => acc.concat([val]);

reducer(acc, val)
///=> 1, 2, 3, 4
``````

`(acc, val) => acc.concat([val])`是一个 reducer，它可以将一个数组和一个值连接。

`(acc, val) => acc.add(val)`也是一个同样的 reducer，`.add`是将一个值插入到这个累积值数组的后面。它适用于任何有`.add`事件，并且执行`.add`事件后返回自身的对象，比如 Set.prototype.add：

``````const acc = new Set([1, 2, 3]);
const val = 4;
const reducer = (acc, val) => acc.add(val);

reducer(acc, val)
///=> Set{1, 2, 3, 4}
``````

``````const toArray = iterable => {
const reducer = (acc, val) => acc.concat([val]);
const seed = [];
let accumulation = seed;

for (value of iterable) {
accumulation = reducer(accumulation, value);
}

return accumulation;
}

toArray([1, 2, 3])
//=> [1, 2, 3]
``````

``````const reduce = (iterable, reducer, seed) => {
let accumulation = seed;

for (const value of iterable) {
accumulation = reducer(accumulation, value);
}

return accumulation;
}

reduce([1, 2, 3], (acc, val) => acc.concat([val]), [])
//=> [1, 2, 3]
``````

``````const reduceWith = (reducer, seed, iterable) => {
let accumulation = seed;

for (const value of iterable) {
accumulation = reducer(accumulation, value);
}

return accumulation;
}

reduce([1, 2, 3], (acc, val) => acc.concat([val]), [])
//=> [1, 2, 3]

// becomes:

reduceWith((acc, val) => acc.concat([val]), [], [1, 2, 3])
//=> [1, 2, 3]
``````

``````[1, 2, 3].reduce((acc, val) => acc.concat([val]), [])
//=> [1, 2, 3]
``````

``````const arrayOf = (acc, val) => { acc.push(val); return acc; };

reduceWith(arrayOf, [], [1, 2, 3])
//=> [1, 2, 3]
``````

``````const sumOf = (acc, val) => acc + val;

reduceWith(sumOf, 0, [1, 2, 3])
//=> 6
``````

### 装饰reducer

``````const joinedWith =
separator =>
(acc, val) =>
acc == '' ? val : `\${acc}\${separator}\${val}`;

reduceWith(joinedWith(', '), '', [1, 2, 3])
//=> "1, 2, 3"

reduceWith(joinedWith('.'), '', [1, 2, 3])
//=> "1.2.3"
``````

JavaScript编写以函数为参数的函数也很容易。

``````const incrementSecondArgument =
binaryFn =>
(x, y) => binaryFn(x, y + 1);

const power =
(base, exponent) => base ** exponent;

const higherPower = incrementSecondArgument(power);

power(2, 3)
//=> 8

higherPower(2, 3)
//=> 16
``````

`power`被装饰后，指数（第二个参数）+1，返回一个方法，我们将它赋值给`higherPower`。因此，调用`higherPower(2, 3)`其实是调用了`power(2, 4)`。当然，我们一直在使用`binaryFn`。这里的 reducer 是 `binaryFn`。我们可以装饰它吗？当然！

``````reduceWith(incrementSecondArgument(arrayOf), [], [1, 2, 3])
//=> [2, 3, 4]

const incremented =
iterable =>
reduceWith(incrementSecondArgument(arrayOf), [], iterable);

incremented([1, 2, 3])
//=> [2, 3, 4]
``````

### mappers

``````const incrementSecondArgument =
binaryFn =>
(x, y) => binaryFn(x, y + 1);
``````

``````const incrementValue =
reducer =>
(acc, val) => reducer(acc, val + 1);
``````

``````const map =
fn =>
reducer =>
(acc, val) => reducer(acc, fn(val));

const incrementValue = map(x => x + 1);

reduceWith(incrementValue(arrayOf), [], [1, 2, 3])
//=> [2, 3, 4]
``````

``````reduceWith(map(x => x + 1)(arrayOf), [], [1, 2, 3])
//=> [2, 3, 4]
``````

``````reduceWith(map(x => x + 1)(joinedWith('.')), '', [1, 2, 3])
//=> "2.3.4"

reduceWith(map(x => x + 1)(sumOf), 0, [1, 2, 3])
//=> 9
``````

``````const squares = map(x => power(x, 2));
const one2ten = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

reduceWith(squares(sumOf), 0, one2ten)
//=> 385
``````

### 过滤器

``````const arrayOf = (acc, val) => { acc.push(val); return acc; };

reduceWith(arrayOf, 0, one2ten)
//=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
``````

``````const bigUns = (acc, val) => {
if (val > 5 ) {
acc.push(val);
}
return acc;
};

reduceWith(bigUns, [], one2ten)
//=> [6, 7, 8, 9, 10]
``````

``````reduceWith(squares(bigUns), [], one2ten)
//=> [9, 16, 25, 36, 49, 64, 81, 100]
``````

``````reduceWith(squares(arrayOf), [], one2ten)
//=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

const bigUnsOf =
reducer =>
(acc, val) =>
(val > 5) ? reducer(acc, val) : acc;

reduceWith(bigUnsOf(squares(arrayOf)), [], one2ten)
//=> [36, 49, 64, 81, 100]
``````

`bgUnsOf`是相当具体的。我们可以将它写成`map`一样，我们来提取判定函数：

``````reduceWith(squares(arrayOf), [], one2ten)
//=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

const filter =
fn =>
reducer =>
(acc, val) =>
fn(val) ? reducer(acc, val) : acc;

reduceWith(filter(x => x > 5)(squares(arrayOf)), [], one2ten)
//=> [36, 49, 64, 81, 100]
``````

``````reduceWith(filter(x => x % 2 === 1)(arrayOf), [], one2ten)
//=> [1, 3, 5, 7, 9]
``````

``````reduceWith(filter(x => x % 2 === 1)(squares(sumOf)), 0, one2ten)
//=> 165
``````

### “转换器”和组合

``````const plusFive = x => x + 5;
const divideByTwo = x => x / 2;

plusFive(3)
//=> 8

divideByTow(8)
//=> 4

const compose2 =
(a, b) =>
(...c) =>
a(b(...c));

const plusFiveDividedByTwo = compose2(divideByTwo, plusFive);

plusFiveDividedByTwo(3)
//=> 4
``````

``````const squaresOfTheOddNumbers = compose2(
filter(x => x % 2 === 1),
squares
);

reduceWith(squaresOfTheOddNumbers(sumOf), 0, one2ten)
//=> 165
``````

`squaresOfTheOddNumbers`转换器是由filter和mapper合并而来。

### 通过转换器组合

```````const compositionOf = (acc, val) => (...args) => val(acc(...args));`
``````

``````const compose = (...fns) =>
reduceWith(compositionOf, x => x, fns);
``````

### 所以，什么是transducer呢？

```````reduceWith(squaresOfTheOddNumbers(sumOf), 0, one2ten)`
``````

``````const transduce = (transformer, reducer, seed, iterable) => {
const transformedReducer = transformer(reducer);
let accumulation = seed;

for (const value of iterable) {
accumulation = transformedReducer(accumulation, value);
}

return accumulation;
}

transduce(squaresOfTheOddNumbers, sumOf, 0, one2ten)
//=> 165
``````

transducer模式的优雅之处在于转换器自然组合生产新的转换器。所以我们可以连接尽可能多的transformer，最终得到一个转换过的reducer，我们只需要迭代一次集合。我们不需要为集合创建副本，也不需要多次迭代数据。

Transducer的概念来自Clojure编程社区，但是您可以发现，它在JavaScript也非常实用，并且使用JavaScript实现起来非常简单。

### 最后

``````const arrayOf = (acc, val) => { acc.push(val); return acc; };

const sumOf = (acc, val) => acc + val;

const setOf = (acc, val) => acc.add(val);

const map =
fn =>
reducer =>
(acc, val) => reducer(acc, fn(val));

const filter =
fn =>
reducer =>
(acc, val) =>
fn(val) ? reducer(acc, val) : acc;

const compose = (...fns) =>
fns.reduce((acc, val) => (...args) => val(acc(...args)), x => x);

const transduce = (transformer, reducer, seed, iterable) => {
const transformedReducer = transformer(reducer);
let accumulation = seed;

for (const value of iterable) {
accumulation = transformedReducer(accumulation, value);
}

return accumulation;
}
``````

### transducer方法来跟踪用户转换

（请参阅使用迭代器编写高度可编写的代码。）

``````const logContents = `1a2ddc2, 5f2b932
f1a543f, 5890595
3abe124, bd11537
f1a543f, 5f2b932
f1a543f, bd11537
f1a543f, 5890595
1a2ddc2, bd11537
1a2ddc2, 5890595
3abe124, 5f2b932
f1a543f, 5f2b932
f1a543f, bd11537
f1a543f, 5890595
1a2ddc2, 5f2b932
1a2ddc2, bd11537
1a2ddc2, 5890595`;

const asStream = function * (iterable) { yield * iterable; };

const lines = str => str.split('\n');
const streamOfLines = asStream(lines(logContents));

const datums = str => str.split(', ');
const datumize = map(datums);

const userKey = ([user, _]) => user;

const pairMaker = () => {
let wip = [];

return reducer =>
(acc, val) => {
wip.push(val);

if (wip.length === 2) {
const pair = wip;
wip = wip.slice(1);
return reducer(acc, pair);
} else {
return acc;
}
}
}

const sortedTransformation =
(xfMaker, keyFn) => {
const decoratedReducersByKey = new Map();

return reducer =>
(acc, val) => {
const key = keyFn(val);
let decoratedReducer;

if (decoratedReducersByKey.has(key)) {
decoratedReducer = decoratedReducersByKey.get(key);
} else {
decoratedReducer = xfMaker()(reducer);
decoratedReducersByKey.set(key, decoratedReducer);
}

return decoratedReducer(acc, val);
}
}

const userTransitions = sortedTransformation(pairMaker, userKey);

const justLocations = map(([[u1, l1], [u2, l2]]) => [l1, l2]);

const stringify = map(transition => transition.join(' -> '));

const transitionKeys = compose(
stringify, justLocations, userTransitions, datumize
);

const countsOf =
(acc, val) => {
if (acc.has(val)) {
acc.set(val, 1 + acc.get(val));
} else {
acc.set(val, 1);
}
return acc;
}

const greatestValue = inMap =>
Array.from(inMap.entries()).reduce(
([wasKeys, wasCount], [transitionKey, count]) => {
if (count < wasCount) {
} else if (count > wasCount) {
return [new Set([transitionKey]), count];
} else {
}
}
, [new Set(), 0]
);

greatestValue(
transduce(transitionKeys, countsOf, new Map(), streamOfLines)
)
//=>
[
"5f2b932 -> bd11537",
"bd11537 -> 5890595"
],
4
``````

### 笔记

1. `(acc, val) => (acc.push(val), acc)` 在语义是令人赏心悦目的，但是不经常使用逗号操作符会有些不习惯，最好避免在生产代码中。
2. 在一些编程社区中，对于字符有很强的保护意识，所以tarnsformer缩写为xform甚至xf。如果你看到像`(xf,reduce,seed,coll)``xf((val,acc) => acc) -> (val,acc) => acc`不要惊讶。这篇文章没有这么写，我们在生产代码中也没有像xf或xform这样的名称。