Transducer 介绍
Aug 05, 2021
给你一个全是整数的数组,要求过滤出所有的偶数,然后将它们翻倍,你会怎么写?
最简单的就是用 for 循环
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]
const results = []
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
results.push( numbers[i] * 2 )
}
}
这段代码很简单,但是如果不仔细读代码,你完全不知道这段代码的目的是什么。
我们可以采用更加声明式的写法
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]
const results = numbers
.filter(n => n % 2 === 0)
.map(n => n * 2)
这段代码清晰多了,一眼就能看出来在干什么,但是它很慢,因为他需要遍历两次数组。
我们可以使用 transducer 来兼顾性能和可读性。transducer 就是高阶 reducer,它入参是一个 reducer,返回一个 reducer。
type Reducer<T = unknown, U = unknown> = (acc: T, cur: U) => T;
type Transducer = (r: Reducer) => Reducer;
type MapFn<T, U> = (a: T) => U;
type Predicate<T> = (a: T) => boolean;
const curry = (
f: (...args: unknown[]) => unknown,
arr: unknown[] = []
) => (...args: unknown[]) => (
a => a.length === f.length ?
f(...a) :
curry(f, a)
)([...arr, ...args]);
const pipe = (...fns: Array<(a: unknown) => unknown>) =>
(x: unknown) => fns.reduceRight((y, f) => f(y), x);
const mapTransducer = <T, U, R>(fn: MapFn<T, U>) =>
(next: Reducer<R, U>) =>
(acc: R, cur: T) => next(acc, fn(cur))
const filterTransducer = <T, U>(fn: Predicate<T>) =>
(next: Reducer<U, T>) =>
(acc: U, cur: T) =>
fn(cur) ? next(acc, cur) : acc
const isEven = (n: number) => n % 2 === 0
const double = (n: number) => n * 2
const arrayConcat: Reducer = <T>(a: T[], c: T) => a.concat([c])
const doubleEvens = pipe(
filterTransducer(isEven),
mapTransducer(double)
);
const xform = doubleEvens(arrayConcat);
const data = [1, 2, 3, 4, 5, 6]
const result = data.reduce(xform, []); // [4, 8, 12]
// 上面的代码等价于
// const result = data.reduce((result, cur) => {
// return isEven(cur) ? arrayConcat(result, double(cur)) : result
// }, [])
const transduce = curry(
<T, U>(reducer: Reducer<U, T>, initial: U, source: T[]) => {
return source.reduce(reducer, initial)
}
)
// 只会遍历一遍数组
console.log(transduce(xform, [], data)) // [4, 8, 12]