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


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

обновление

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

12 81

12 ответов:

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

они композиционны и полиморфны.

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

Ad Обновление

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

  1. вложенных вызовов
    (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)

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

это приличный прогон.

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

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

датчики середины комбинации для уменьшения функций.

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

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

(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]

они также не зависят от того, какой источник входного сигнала.

множественные датчики можно приковать как (chainable) рецепт для того чтобы преобразовать уменьшая функции.

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

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

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

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

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

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

Рич Хикки выступил с докладом "датчики" на конференции Strange Loop 2014 (45 мин).

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

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

Я нашел примеры чтения из преобразователи-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 выглядит намного чище, чем обычная альтернатива с подчеркиванием.

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

датчики (в моем понимании!) функции, которые принимают один сокращение функция и возврат другой. Функция сокращения - это та, которая

например:

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-transducer принимает функцию фильтрации входных данных, которая применяется к 0, то если это значение четное? в первом случае фильтр передает это значение счетчику, а затем фильтрует следующее значение. Вместо того, чтобы сначала фильтровать, а затем передавать все эти значения рассчитывать.

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

датчик четкое определение здесь:

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

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

надеюсь, что это помогает.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

https://medium.com/@roman01la/understanding-transducers-in-javascript-3500d3bd9624

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

по мере того как вставка деятельности нет в коде входного сигнала и ничего не сделано с выходом, датчики весьма многоразовы. Они напоминают мне поток s в Akka Streams.

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