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))
;; => 106into позволяет применять трансдьюсер к входной коллекции и создать на основе результата применения трансдьюсера новую коллекцию. Старайтесь по возможности использовать эту функцию, так как она применяет 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, а затем выбор только четных элементов (фильтрация).
Полезное
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))
;; => 106into позволяет применять трансдьюсер к входной коллекции и создать на основе результата применения трансдьюсера новую коллекцию. Старайтесь по возможности использовать эту функцию, так как она применяет 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, а затем выбор только четных элементов (фильтрация).
Полезное
Ваше упражнение проверяется по этим тестам
(ns transducers-test
(:require [test-helper :refer [assert-solution]]
[index :refer [my-xf]]))
(assert-solution
[[(range 10)] [(range 50)] [(range 90)]]
[90 2450 8010]
#(transduce my-xf + %))
(assert-solution
[[(range 5)] [(range 10)]]
[[0 2 4 6 8] [0 2 4 6 8 10 12 14 16 18]]
#(into [] my-xf %))Решение учителя откроется через:
20:00
