Преобразователи используют тот факт, что композиция функций абстрагируется от арности, т.е. может возвращать функцию вместо «нормального значения»:
const comp = f => g => x => f(g(x));
const add = x => y => x + y;
const sqr = x => x * x;
const add9 = comp(add) (sqr) (3); // returns a lambda
console.log(
add9(6)); // 15
Теперь сам преобразователь довольно скучен:
reduce => acc => x => /* body is specific to the transducer at hand */
Это просто замыкание, которое ожидает редуктор (то есть двоичная функция, которая объединяет свои двааргументы) и затем могут быть переданы непосредственно в вашу любимую сокращающую функцию.
Давайте посмотрим на преобразователь карты:
const mapper = f => (reduce => acc => x =>
reduce(acc) (f(x)));
Избыточные скобки просто иллюстрируют замыкание преобразователя.В этом случае он закрывается над f
, нашей функцией преобразования.Далее мы собираемся применить его:
// map transducer
const mapper = f => reduce => acc => x =>
reduce(acc) (f(x));
// my favorite fold (reducing function)
const arrFold = alg => zero => xs => {
let acc = zero;
for (let i = 0; i < xs.length; i++)
acc = alg(acc) (xs[i], i);
return acc;
};
// reducer
const add = x => y => x + y;
// transformer
const sqr = x => x * x;
// MAIN
const main = arrFold(mapper(sqr) (add)) (0);
console.log(
main([1,2,3])); // 14
Ну, не так впечатляет, верно?Реальная мощность преобразователей обусловлена их сочетанием с функциональным составом:
// map transducer
const mapper = f => reduce => acc => x =>
reduce(acc) (f(x));
// filter transducer
const filterer = p => reduce => acc => x =>
p(x) ? reduce(acc) (x) : acc;
// my favorite fold (reducing function)
const arrFold = alg => zero => xs => {
let acc = zero;
for (let i = 0; i < xs.length; i++)
acc = alg(acc) (xs[i], i);
return acc;
};
// helpers
const add = x => y => x + y; // reducer
const sqr = x => x * x; // transformer
const isOdd = x => (x & 1) === 1; // predicate
const comp = f => g => x => f(g(x));
// MAIN
const main = arrFold(comp(filterer(isOdd)) (mapper(sqr)) (add)) (0);
console.log(
main([1,2,3])); // 10
Несмотря на то, что у нас есть два преобразователя, через Array
есть только один обход.Это свойство называется циклическим слиянием.Поскольку композиция преобразователя возвращает другую функцию, порядок оценки меняется на противоположный, то есть идет слева направо, тогда как композиция функции обычно идет справа налево.
Повторное использование является еще одним преимуществом.Вы должны определить преобразователи только один раз, и можете использовать их один раз и использовать все складываемые типы данных.
Также следует отметить, что transduce
- это просто вспомогательная функция и не важна для понимания концепции.
Это почти то же самое, что сказать о преобразователях.