Может ли кто-нибудь объяснить мне преобразователи Clojure в простых терминах?

Я пробовал прочитать об этом, но я до сих пор не понимаю их стоимости или того, что они заменяют. И делают ли они мой код короче, понятнее или что?

Update

Многие из пользователей разместили ответы, но было бы неплохо увидеть примеры с и без преобразователей для чего-то очень простого, что даже идиот, как я, может понять. Если, конечно, датчики не нуждаются в определенном высоком уровне понимания, в этом случае я их никогда не пойму: (

Ответ 1

Преобразователи - это рецепты, что делать с последовательностью данных без знания того, что является базовой (как это сделать). Это может быть любой seq, асинхронный канал или, возможно, наблюдаемый.

Они являются составными и полиморфными.

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

Обновление объявлений

В предыдущей версии 1.7 из Clojure у вас было три способа записать запросы потока данных:

  • вложенные вызовы
    (reduce + (filter odd? (map #(+ 2 %) (range 0 10))))
  1. функциональная композиция
    (def xform
      (comp
        (partial filter odd?)
        (partial map #(+ 2 %))))
    (reduce + (xform (range 0 10)))
  1. макрокоманда
    (defn xform [xs]
      (->> xs
           (map #(+ 2 %))
           (filter odd?)))
    (reduce + (xform (range 0 10)))

С преобразователями вы напишете это как:

(def xform
  (comp
    (map #(+ 2 %))
    (filter odd?)))
(transduce xform + (range 0 10))

Все они делают то же самое. Разница в том, что вы никогда не называете преобразователи напрямую, вы передаете их другой функции. Преобразователи знают, что делать, функция, которая получает преобразователь, знает, как это сделать. Порядок комбинаторов похож на то, что вы пишете его с помощью макросов (естественного порядка). Теперь вы можете повторно использовать xform с каналом:

(chan 1 xform)

Ответ 2

Преобразователи повышают эффективность и позволяют писать эффективный код более модульным способом.

Это достойный прогон через.

По сравнению с составлением вызовов старых map, filter, reduce и т.д. вы получаете лучшую производительность, потому что вам не нужно создавать промежуточные коллекции между каждым шагом и многократно ходить по этим коллекциям.

По сравнению с reducers или вручную составляя все ваши операции в одном выражении, вам легче использовать абстракции, улучшенную модульность и повторное использование функций обработки.

Ответ 3

Преобразователи являются средством объединения для уменьшения функций.

Пример: Сокращение функций - это функции, которые принимают два аргумента: результат до сих пор и ввод. Они возвращают новый результат (пока). Например +: с двумя аргументами вы можете думать о первом как итоговом, а второй - как о вводе.

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

(defn double
  [rfn]
  (fn [r i] 
    (rfn r (* 2 i))))

Для иллюстрации замените rfn на +, чтобы увидеть, как + преобразуется в два раза:

(def twice-plus ;; result of (double +)
  (fn [r i] 
    (+ r (* 2 i))))

(twice-plus 1 2)  ;-> 5
(= (twice-plus 1 2) ((double +) 1 2)) ;-> true

Итак,

(reduce (double +) 0 [1 2 3]) 

теперь даст 12.

Уменьшение функций, возвращаемых преобразователями, не зависит от того, как накапливается результат, потому что они накапливаются с передаваемой им функцией уменьшения, неосознанно. Здесь мы используем conj вместо +. conj берет коллекцию и значение и возвращает новую коллекцию с добавленным значением.

(reduce (double conj) [] [1 2 3]) 

даст [2 4 6]

Они также не зависят от источника ввода.

Несколько преобразователей могут быть закодированы в виде (цепочки) рецепта для преобразования сокращающих функций.

Обновление: поскольку сейчас есть официальная страница, я настоятельно рекомендую прочитать ее: http://clojure.org/transducers

Ответ 4

Допустим, вы хотите использовать ряд функций для преобразования потока данных. Оболочка Unix позволяет вам делать такие вещи с оператором канала, например

cat /etc/passwd | tr '[:lower:]' '[:upper:]' | cut -d: -f1| grep R| wc -l

(Приведенная выше команда подсчитывает количество пользователей с буквой r в upper- или в нижнем регистре в их имени пользователя). Это реализовано в виде набора процессов, каждый из которых считывает выходные данные предыдущих процессов, поэтому существует четыре промежуточных потока. Можно представить другую реализацию, которая объединяет пять команд в одну агрегированную команду, которая будет считывать из ее входных данных и записывать свои выходные данные только один раз. Если бы промежуточные потоки были дорогими, а состав был дешевым, это могло бы быть хорошим компромиссом.

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

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

Это довольно хороший обзор преобразователей.

Ответ 5

Богатый Хикки дал "Передачи" на конференции Strange Loop 2014 (45 минут).

Он просто объясняет, что такое преобразователи, с примерами в реальном мире - обработка пакетов в аэропорту. Он четко разделяет различные аспекты и противопоставляет их нынешним подходам. К концу он дает обоснование для их существования.

Видео: https://www.youtube.com/watch?v=6mTbuzafcII

Ответ 6

Я нашел примеры чтения из transducers-js помогает мне понять их в конкретных терминах того, как я могу использовать их в повседневной жизни, дневного кода.

Например, рассмотрим этот пример (взятый из README по ссылке выше):

var t = require("transducers-js");

var map    = t.map,
    filter = t.filter,
    comp   = t.comp,
    into   = t.into;

var inc    = function(n) { return n + 1; };
var isEven = function(n) { return n % 2 == 0; };
var xf     = comp(map(inc), filter(isEven));

console.log(into([], xf, [0,1,2,3,4])); // [2,4]

Для одного, использование xf выглядит намного чище, чем обычная альтернатива с помощью Underscore.

_.filter(_.map([0, 1, 2, 3, 4], inc), isEven);

Ответ 7

Преобразователи - это (для моего понимания!) функции, которые принимают одну снижающую функцию и возвращают другую. Функция уменьшения - это функция, которая

Например:

user> (def my-transducer (comp count filter))
#'user/my-transducer
user> (my-transducer even? [0 1 2 3 4 5 6])
4
user> (my-transducer #(< 3 %) [0 1 2 3 4 5 6])
3

В этом случае my-преобразователь принимает входную функцию фильтрации, которая применяется к 0, если это значение четное? в первом случае фильтр передает это значение счетчику, затем он фильтрует следующее значение. Вместо первой фильтрации и передачи всех этих значений для подсчета.

Во втором примере это одно и то же: он проверяет одно значение за раз, и если это значение меньше 3, то позволяет подсчитать добавление 1.

Ответ 8

Определение четности преобразователя находится здесь:

Transducers are a powerful and composable way to build algorithmic transformations that you can reuse in many contexts, and they’re coming to Clojure core and core.async.

Чтобы понять это, рассмотрим следующий простой пример:

;; The Families in the Village

(def village
  [{:home :north :family "smith" :name "sue" :age 37 :sex :f :role :parent}
   {:home :north :family "smith" :name "stan" :age 35 :sex :m :role :parent}
   {:home :north :family "smith" :name "simon" :age 7 :sex :m :role :child}
   {:home :north :family "smith" :name "sadie" :age 5 :sex :f :role :child}

   {:home :south :family "jones" :name "jill" :age 45 :sex :f :role :parent}
   {:home :south :family "jones" :name "jeff" :age 45 :sex :m :role :parent}
   {:home :south :family "jones" :name "jackie" :age 19 :sex :f :role :child}
   {:home :south :family "jones" :name "jason" :age 16 :sex :f :role :child}
   {:home :south :family "jones" :name "june" :age 14 :sex :f :role :child}

   {:home :west :family "brown" :name "billie" :age 55 :sex :f :role :parent}
   {:home :west :family "brown" :name "brian" :age 23 :sex :m :role :child}
   {:home :west :family "brown" :name "bettie" :age 29 :sex :f :role :child}

   {:home :east :family "williams" :name "walter" :age 23 :sex :m :role :parent}
   {:home :east :family "williams" :name "wanda" :age 3 :sex :f :role :child}])

Как насчет того, что мы хотим узнать, сколько детей в деревне? Мы можем легко найти его со следующим редуктором:

;; Example 1a - using a reducer to add up all the mapped values

(def ex1a-map-children-to-value-1 (r/map #(if (= :child (:role %)) 1 0)))

(r/reduce + 0 (ex1a-map-children-to-value-1 village))
;;=>
8

Вот еще один способ сделать это:

;; Example 1b - using a transducer to add up all the mapped values

;; create the transducers using the new arity for map that
;; takes just the function, no collection

(def ex1b-map-children-to-value-1 (map #(if (= :child (:role %)) 1 0)))

;; now use transduce (c.f r/reduce) with the transducer to get the answer 
(transduce ex1b-map-children-to-value-1 + 0 village)
;;=>
8

Кроме того, он очень эффективен при учете подгрупп. Например, если мы хотим узнать, сколько детей находится в семье Брауна, мы можем выполнить:

;; Example 2a - using a reducer to count the children in the Brown family

;; create the reducer to select members of the Brown family
(def ex2a-select-brown-family (r/filter #(= "brown" (string/lower-case (:family %)))))

;; compose a composite function to select the Brown family and map children to 1
(def ex2a-count-brown-family-children (comp ex1a-map-children-to-value-1 ex2a-select-brown-family))

;; reduce to add up all the Brown children
(r/reduce + 0 (ex2a-count-brown-family-children village))
;;=>
2

Надеюсь, вы найдете полезные примеры. Вы можете найти более здесь

Надеюсь, что это поможет.

Клеменсио Моралес Лукас.

Ответ 9

Я написал об этом с помощью примера clajurescript , в котором объясняется, как функции последовательности теперь расширяемы благодаря возможности замены функции уменьшения.

Это точка преобразователей, когда я ее читал. Если вы думаете о операции cons или conj, которая жестко закодирована в таких операциях, как map, filter и т.д., Функция уменьшения недоступна.

С преобразователями функция уменьшения отключена, и я могу заменить ее, как я сделал, с собственным массивом javascript push благодаря преобразователям.

(transduce (filter #(not (.hasOwnProperty prevChildMapping %))) (.-push #js[]) #js [] nextKeys)

filter, а у друзей новая операция 1 arity, которая вернет функцию трансдуцирования, которую вы можете использовать для предоставления своей собственной функции уменьшения.

Ответ 10

Здесь мой (главным образом) жаргон и бесплатный код.

Подумайте о данных двумя способами: потоке (значения, которые происходят со временем, например события) или структуре (данные, которые существуют в определенный момент времени, например, список, вектор, массив и т.д.).

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

Функция сопоставления является лишь одним из классов функций, которые иногда называют "сокращающими функциями". Другой общей функцией уменьшения является фильтр, который удаляет значения, соответствующие предикату (например, удаляют все значения, которые являются четными).

Преобразователи позволяют "обернуть" последовательность одной или нескольких восстанавливающих функций и создать "пакет" (который сам по себе является функцией), который работает как с потоками, так и с структурами. Например, вы можете "упаковать" последовательность сокращающих функций (например, фильтровать четные числа, а затем отображать полученные числа, чтобы увеличить их на 1), а затем использовать "пакет" преобразователя в потоке или структуре значений (или обоих).

Итак, что особенного в этом? Как правило, сокращение функций, которые не могут быть эффективно составлены для работы как с потоками, так и с структурами.

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

Ответ 12

Насколько я понимаю, они как строительные блоки, отделенные от реализации ввода и вывода. Вы просто определяете операцию.

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

Я также новичок в преобразователях, извините за возможно неясный ответ.