Clojure: Трансдьюсеры

Как уже было упомянуто, по умолчанию обработка коллекций в Clojure знаменитой тройкой map, filter и reduce иммутабельна, что иногда может сильно увеличить расход памяти и время обработки коллекции большого размера. Однако в таких ситуациях на помощь приходят трансдьюсеры.

Трансдьюсеры - это составные алгоритмические преобразования над коллекциями. Они независимы от контекста своих входных и выходных параметров и задают только суть преобразования в терминах отдельного элемента коллекции. Поскольку трансдьюсеры не зависят от ввода или вывода, их можно использовать в различных процессах - коллекциях, потоках, каналах и т. д.

Редукционная функция - это такая функция, которую вы передаете для редукции, то есть - это функция, которая принимает накопленный результат и новую коллекцию и возвращает новый накопленный результат:

;; сигнатура редукционной функции
что угодно, вход -> что угодно

Трансдьюсер (иногда называемый xform или xf) - это преобразование одной редуцирующей функции в другую:

;; сигнатура преобразователя
(что угодно, вход -> что угодно) -> (что угодно, вход -> что угодно)

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

(filter odd?) ;; этот вызов возвращает трансдьюсер, который фильтрует нечетные числа
(map inc)     ;; этот вызов возвращает трансдьюсер, который инкрементирует все числа на 1
(take 5)      ;; этот вызов возвращает трансдьюсер, который берет из коллекции первые 5 чисел

Трансдьюсеры компонуются с помощью обычной функциональной композиции. Рекомендуемый способ компоновки трансдьюсеров - это использование существующей функции comp:

(def xf
  (comp
    (filter odd?)
    (map inc)
    (take 5)))

Трансдьюсер xf - это стек преобразований, который будет применен к переданным элементам. Каждая функция в стеке выполняется перед операцией, которую она обертывает. Композиция трансдьюсеров выполняется справа налево, но строит стек преобразований, который выполняется слева направо (в этом примере фильтрация происходит перед маппингом).

В качестве мнемоники запомните, что упорядочивание функций трансдьюсеров в comp имеет тот же порядок, что и преобразования последовательности в макросе ->>. Пример выше эквивалентен следующему примеру:

(->> coll
  (filter odd?)
  (map inc)
  (take 5))

Функции, которые можно использовать в качестве трансдьюсеров: map, cat, mapcat, filter, remove, take, take-while, take-nth, drop, drop-while, replace, partition-by, partition-all, keep, keep-indexed, map-indexed, distinct, interpose, dedupe, random-sample.

А теперь посмотрим, как применять трансдьюсеры. Самым распространенным способом применения трансдьюсеров является использование функции transduce, которая является аналогом функции reduce, только для трансдьюсеров:

(transduce xform f coll)
(transduce xform f init coll)

transduce нелениво редуцирует coll с помощью преобразователя xform, применяемого к редуцирующей функции f, используя init в качестве начального значения, если оно предоставлено, или f в противном случае. f функция знает о том, как накапливать результат. А теперь примеры:

(def xf (comp (filter odd?) (map inc)))

(transduce xf + (range 5))
;; => 6

(transduce xf + 100 (range 5))
;; => 106

into позволяет применять трансдьюсер к входной коллекции и создать на основе результата применения трансдьюсера новую коллекцию. Старайтесь по возможности использовать эту функцию, так как она применяет reduce эффективнее (без промежуточных коллекций), если есть такая возможность.

(into [] xf (range 10))
[2 4 6 8 10]

(into () xf (range 10)) ; => в списки элементы добавляются иначе, поэтому итоговый список в обратном порядке
(10 8 6 4 2)

(into #{} xf (range 10))
#{4 6 2 10 8}

Про создание трансдьюсеров поговорим в следующем упражнении.

Задание

Скомбинируйте несколько трансдьюсеров в один my-xf, в котором происходит умножение всех элементов на 10, затем деление на 5, а затем выбор только четных элементов (фильтрация).

Полезное

Команда проекта находится в телеграм-сообществе. Там можно задать любой вопрос и повлиять на проект

Нашли ошибку? Есть что добавить? Пулреквесты приветствуются