Трансдьюсеры в Javascript

Ruslan Rashidov
4 min readNov 1, 2020

--

Photo by Ivan Torres on Unsplash

Трансдьюсеры — это функции которые на вход получают редьюсер и возвращают так же редьюсер. Чтобы понять для чего они нужны и как их применять, давайте рассмотрим пример.

Допустим у нас есть массив. Значениями массива будут объекты описывающие пиццу🍕🍕🍕:

Добавим грибов, отфильтруем пиццы маленького размера и добавим помидоры:

Основная проблема в данном случае — это промежуточные массивы. Между вызовом каждой из функций map, filter, map создается промежуточный массив.

Это не так страшно, если в массиве мало элементов, но, что если массив содержит 100000 элементов или больше. Мы создаем 2 промежуточных массива по 100000 элементов, что отрицательно сказывается на производительности и потребляемой памяти.

Здесь то на помощь к нам и приходят трансдьюсеры. Они позволяют производить выше описанные трансформации над массивом без создания дополнительных промежуточных массивов.

Итак, как все это работает?

Для начала надо ознакомится с такой функцией как compose. Она необходима для создания композиции функций. Что такое композиция функций? foo(bar(baz(value))) вот композиция функций. С помощью compose это будет выглядеть вот так:

Ниже показан один из вариантов реализации функции compose:

Как мы знаем, любую функцию map или filter можно заменить функцией reduce. Reduce — это функция, которая первым аргументом принимает функцию редьюсер, а вторым аргументом объект, используемый в качестве первого аргумента редьюсера. Перепишем наш код с использованием функции reduce. Для удобства не будем подключать второй map с помидорами:

Для наглядности вынесем редьюсеры в отдельные функции:

К сожалению, мы не можем использовать композицию функций с редьюсерами, т.к. редьюсер принимает два параметра, а возвращает один, т.е. следующий код не будет работать:

Будем исправлять эту ситуацию. Для этого вынесем общую часть наших редьюсеров в отдельную функцию, которая также является редьюсером:

А наши редьюсеры перепишем в более универсальные функции:

Теперь с помощью этих функций мы можем получить наши первоначальные редьюсеры:

Готово, мы написали наши первые трансдьюсеры getMushroomsReducer, getSizeReducer.

Вспомним, трансдьюсер — это функция которая на вход получают редьюсер и возвращают так же редьюсер. Наши функции getMushroomsReducer, getSizeReducer получают на вход редьюсеры и возвращают так же редьюсеры. А это значит, что теперь мы можем использовать композицию с нашими трансдьюсерами. То есть, мы можем переписать вышестоящий код следующим образом:

Или с использованием нашей функции compose:

Рассмотрим как это работает подробнее:

Теперь, когда мы разобрались почему композиция работает, преобразуем наши трансдьюсеры, в более универсальные функции. Для этого вынесем для map трансдьюсера ту часть, которая отвечает за трансформацию значения, а в filter трансдьюсере ту часть, которая отвечает за условный выбор:

addMushrooms и filterSmallPizza — это те функции которые мы передавали в качестве первого аргумента для map и filter в начале статьи, т.е.

Теперь мы можем заменить этот код трансдьюсерами, который будет делать все тоже самое, но без промежуточных массивов:

Рассмотрим подробнее как это работает на диаграмме:

Функция compose вызывает функции справа на лево, таким образом последней вызывается sizeTransducer, которая возвращает редьюсер, который мы передаем в функцию reduce. Затем, будет вызван редьюсер возвращаемый функцией mushroomsTransducer и последней вызывается функция arrReducer, которая и производит изменения над вторым параметром функции reduce, в данном случае это пустой массив []. Таким образом, значения массива pizzas проходят преобразования, не создавая при этом промежуточные массивы.

В данной статье я попытался объяснить, что такое трансдьюсеры и как они работают. Для того чтобы использовать трансдьюсеры, в вашем проекте, вам не обязательно их реализовывать самому, трансдьюсеры и многие другие полезные функции реализованы в библиотеке для функционального программирования Ramda.

Весь код который использовался в данной статье, вы можете скачать в репозитории https://github.com/tessierashpool/transducers-article. Для удобства каждая ветка репозитория представляет собой шаг описываемый в статье. Ветка final содержит код для замера производительности кода, надписанного обычным способом и с помощью трансдьюсеров.

Трансдьюсеры — это только часть инструментов, которое предоставляет функциональное программирование. Если вас заинтересовало функциональное программирование, рекомендую вам прочитать книгу Composing Software by Eric Elliott.

--

--

Responses (1)