Давайте начнем с определения: transducer
- это функция, которая принимает reducer
функцию и возвращает reducer
функцию.
A reducer
- это двоичная функция, которая принимает аккумулятор и значение и возвращает аккумулятор. Редуктор может быть выполнен с функцией reduce
(примечание: все функции каррированы, но я разобрался с этим, также как и определения для pipe
и compose
для удобства чтения - вы можете увидеть их в live демо ):
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]
Преобразователи помогают нам решить эту проблему: когда мы используем преобразователь, не создаются временные массивы, и мы можем обобщать наши функции для работы не только с массивами. Для работы преобразователей требуется функция 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
Мы даже можем определить массив map
и filter
, используя transducer
, потому что он настолько компонуем:
const map = (xf, data) => transduce(mapping(xf), arrReducer, [], data);
const filter = (predicate, data) => transduce(filtering(predicate), arrReducer, [], data);
живая версия, если вы хотите запустить код -> https://runkit.com/marzelin/transducers
Имеет ли смысл мои рассуждения?