Я нашел очень хорошую статью, объясняющую преобразователи. Если вы знакомы с Clojure, идите и прочитайте: «Понимание преобразователей» Анкет Но если вы являетесь разработчиком JavaScript и не используете для чтения кода LISP, я перевел примеры кода из этой статьи в JavaScript. Таким образом, вы все еще можете прочитать статью и увидеть примеры кода здесь.
Что такое датчики?
Быстрое вступление: Преобразователи являются композиционными и эффективными функциями преобразования данных, которые не создают промежуточные коллекции.
На некоторых языках эта оптимизация известна как петля Fusion или потоковой фьюжн Анкет Однако преобразователи предлагают гораздо больше, чем это (по цене чисто оптимизации времени выполнения).
Вот визуализация, чтобы показать разницу между цепными преобразованиями и преобразованным один раз.
Зачем их использовать?
Вышеуказанная визуализация означает, что заданные преобразования, такие как карта, фильтр или, в основном, любая другая операция по последовательности значений, мы хотим составить их вместе и эффективно выполнять каждый кусок данных через них шаг за шагом. Но следующий пример не такого рода композиции:
array .map(fn1) .filter(fn2) .reduce(fn3);
Приведенный выше пример не отделяет преобразование от данных и создает массивы на каждом шаге в цепочке.
Вместо этого мы хотим что -то подобное:
const transformation = compose(map(fn1), filter(fn2), reduce(fn3)); transformation(array);
Таким образом, мы можем повторно использовать трансформацию и составить ее с другими. Чтобы достичь такой композиции, эти функции должны быть обобщены. Оказывается, все они могут быть выражены с точки зрения уменьшения.
Примеры кода из статьи
карта и фильтр, и как их можно объединить:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((x) => x + 1); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].filter((x) => x % 2 === 0); // [2, 4, 6, 8, 10] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] .map((x) => x + 1) .filter((x) => x % 2 === 0); // [2, 4, 6, 8, 10]
Карта и фильтр могут быть реализованы с помощью уменьшения. Вот реализация карты:
const mapIncReducer = (result, input) => result.concat(input + 1); [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].reduce(mapIncReducer, []); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Давайте извлеките функцию увеличения, чтобы позволить ее передавать в редуктор:
const mapReducer = f => (result, input) => result.concat(f(input)); [0, 1, 2, 3, 4, 5, 6].reduce(mapReducer((x) => x + 1), []); // [1, 2, 3, 4, 5, 6, 7]
Больше примеров использования MAP REDUCER:
[0, 1, 2, 3, 4, 5].reduce(mapReducer(x => x - 1), []); // [-1, 0, 1, 2, 3, 4] [0, 1, 2, 3, 4, 5].reduce(mapReducer(x => x * x), []); // [0, 1, 4, 9, 16, 25]
Реализация фильтра с использованием уменьшения:
const filterEvenReducer = (result, input) => input % 2 === 0 ? result.concat(input) : result; [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].reduce(filterEvenReducer, []); // [2, 4, 6, 8, 10]
Опять же, извлеките предикатую функцию, чтобы ее можно было передавать снаружи:
const filterReducer = (predicate) => (result, input) => predicate(input) ? result.concat(input) : result; [1, 2, 3, 4, 5, 6].reduce(filterReducer(x => x % 2 === 0), []); // [2, 4, 6]
Объединить оба редуктора вместе:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] .reduce(mapReducer(x => x + 1), []) .reduce(filterReducer(x => x % 2 === 0), []); // [2, 4, 6, 8, 10]
Похоже на то, что вы обычно делаете со встроенными методами массива:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] .map(x => x + 1) .filter(x => x % 2 === 0); // [2, 4, 6, 8, 10]
Вот оба снова редукторы, и оба они используют Array Concat в качестве восстановительной функции:
const mapReducer = f => (result, input) => result.concat(f(input)); const filterReducer = (predicate) => (result, input) => predicate(input) ? result.concat(input) : result;
CONCAT и + оба снижают операции, они принимают начальное значение и вход и уменьшают их до одного выходного значения:
array.concat(4); // [1, 2, 3, 4] 10 + 1; // 11
Давайте извлеките функцию уменьшения, поэтому она также может быть передана снаружи:
const mapping = f => reducing => (result, input) => reducing(result, f(input)); const filtering = predicate => reducing => (result, input) => predicate(input) ? reducing(result, input) : result;
Вот как сейчас можно использовать редукторы:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] .reduce(mapping(x => x + 1)((xs, x) => xs.concat(x)), []) .reduce(filtering(x => x % 2 === 0)((xs, x) => xs.concat(x)), []); // [2, 4, 6, 8, 10]
Тип подписи редукторов -результат, вход -> Результат:
mapping(x => x + 1)((xs, x) => xs.concat(x))([], 1); // [2] mapping(x => x + 1)((xs, x) => xs.concat(x))([2], 2); // [2, 3] filtering(x => x % 2 === 0)((xs, x) => xs.concat(x))([2, 4], 5); // [2, 4] filtering(x => x % 2 === 0)((xs, x) => xs.concat(x))([2, 4], 6); // [2, 4, 6]
Композиция редукторов имеет точно такой же тип:
mapping(x => x + 1)(filtering(x => x % 2 === 0)((xs, x) => xs.concat(x)));
Таким образом, его также можно использовать в качестве редуктора:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] .reduce(mapping(x => x + 1)(filtering(x => x % 2 === 0)((xs, x) => xs.concat(x))), []); // [2, 4, 6, 8, 10]
Давайте использовать r.comse из библиотеки Ramda для лучшей читаемости:
const xform = R.compose(mapping(x => x + 1), filtering(x => x % 2 === 0)); [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] .reduce(xform((xs, x) => xs.concat(x)), []); // [2, 4, 6, 8, 10]
Более сложный пример:
const square = x => x * x; const isEven = x => x % 2 === 0; const inc = x => x + 1; const xform = R.compose(filtering(isEven), filtering(x => x < 10), mapping(square), mapping(inc)); [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] .reduce(xform((xs, x) => xs.concat(x)), []); // [1, 5, 17, 37, 65]
Наконец, давайте завершим его в функцию транссуции:
const transduce = (xform, reducing, initial, input) => input.reduce(xform(reducing), initial);
Окончательный пример использования:
const xform = R.compose(mapping((x) => x + 1), filtering((x) => x % 2 === 0)); transduce( xform, (xs, x) => xs.concat(x), [], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); // [2, 4, 6, 8, 10] transduce( xform, (sum, x) => sum + x, 0, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); // 30
Проверьте Transducers-JS Библиотека для полной и эффективной реализации преобразователей в JavaScript. Читайте о Протокол преобразователя который обеспечивает безопасную взаимосвязь между библиотеками (например, Lodash, подчеркивается и непредубежден.js).
Преобразователи являются частью стандартной библиотеки в Clojure. Обязательно посмотрите на Clojurescript Анкет
Оригинал: “https://dev.to/romanliutikov/understanding-transducers-in-javascript-4pdg”