Clojure поведение датчиков

С новым clojure 1.7 я решил понять, где можно использовать преобразователи. Я понимаю, какую пользу они могут принести, но не могу найти нормальных примеров написания пользовательских преобразователей с объяснениями.

Хорошо, я пытался проверить, что происходит. Я открыл документацию clojure. А там примеры использования xf в качестве аргумента. Во-первых: что означает этот xf или xfrom? Этот материал производил преобразователь личности.

(defn my-identity [xf]
  (fn 
   ([]
     (println "Arity 0.")
     (xf))
   ([result]
     (println "Arity 1: " result " = " (xf result)) 
     (xf result))
   ([result input]
     (println "Arity 2: " result input " = " (xf result input)) 
     (xf result input))))

Я взял названия переменных [result input] из примера документации. Я думал, что это как в функции уменьшения, где result сокращенная часть и input это новый элемент коллекции.

Поэтому, когда я делаю (transduce my-identity + (range 5)) Я получил результат 10 что я ожидал Тогда я читаю о eduction, но я не могу понять, что это. Во всяком случае, я сделал (eduction my-identity (range 5)) и получил:

Arity 2:  nil 0  =  nil
Arity 2:  nil 1  =  nil
Arity 1: nil  =  nil
(0 0 1 1)

Каждый предмет дублировался, потому что я звоню xf в println заявление. Почему он продублировал каждый предмет дважды? Почему я получил ноль? Я всегда получу ноль, делая образование? Могу ли я передать это поведение?

Во всяком случае, я сделал

> (reduce + (eduction my-identity (range 5))
clojure.core.Eduction cannot be cast to clojure.lang.IReduce

Хорошо, результат Eduction это НЕ сводимо, но напечатано как список. Почему это не сводимо? Когда я печатаю (doc eduction) я понимаю

Returns a reducible/iterable application of the transducers
to the items in coll.

не должны (transduce xform f coll) а также (reduce f (eduction xfrom coll)) быть таким же?

я сделал

> (reduce + (sequence my-identity (range 5))
20

Конечно получил 20 из-за дубликатов. Я опять подумал, что так и должно быть (transduce xform f coll) а также (reduce f (sequence xfrom coll)) быть всегда равным, по крайней мере, в таком маленьком примере без каких-либо датчиков состояния. Это глупо, что их нет, или я не прав?

Хорошо, тогда я попробовал (type (sequence my-identity (range 5))) и получить clojure.lang.LazySeq Я думал, что это лениво, но когда я попытался взять first Элемент clojure рассчитал всю последовательность сразу.

Итак, мое резюме:

1) Что означает xf или xform?

2) Почему я получаю nil как result аргумент в то время как eduction или же sequence?

3) Могу ли я всегда быть уверен, что это будет nil в то время как eduction или же sequence?

4) Что такое eduction и что за идиоматическая идея, которую нельзя привести? Или если это так, то как я могу уменьшить его?

5) Почему я получаю побочные эффекты во время sequence или же eduction?

6) Могу ли я создать настоящие ленивые последовательности с преобразователями?

1 ответ

Решение

Много вопросов, давайте начнем с нескольких ответов:

  1. Да, xf == xform это "преобразователь".
  2. Ваш my-identity функция не компилируется. У вас есть параметр, а затем несколько других функций функции. Я полагаю, вы забыли (fn ...),
  3. Ваш аргумент к вашему преобразователю личности называется xf, Тем не менее, это обычно называется rf, что означает "восстанавливающая функция". Теперь запутанная часть заключается в том, что xf также сокращают функции (следовательно, comp просто работает). Тем не менее, это сбивает с толку, что вы бы назвали это xf и ты должен это назвать rf,

  4. Преобразователи обычно "создаются", так как они могут иметь состояние и / или передаваемые параметры. В вашем случае вам не нужно создавать его, так как он прост и не имеет состояния или даже параметра. Однако имейте в виду, что вы обычно заключаете свою функцию в другую fn возвращающая функция. Это означает, что вам нужно позвонить (my-identity) вместо того, чтобы просто передать его как my-identity, Опять же, здесь все хорошо, просто немного неубедительно и, возможно, сбивает с толку.

  5. Давайте сначала продолжим и сделаем вид, что ваш my-identity преобразователь правильный (это не так, и я объясню позже, что происходит).

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

  7. В начале я думаю, что полезно думать о сокращении функций как о conj (или на самом деле conj!) или в вашем случае +,

  8. Ваш eduction печатает элементы, которые он производит, так как он реализует Iterable который называется println или ваш ответ. Он просто распечатывает каждый элемент, который вы добавляете в свой датчик с помощью вызова arity 2.

  9. Ваш звонок в (reduce + (eduction my-identity (range 5))) не работает с Eduction (объект строится в eduction) только реализует IReduceInit, IReduceInit как следует из названия, требует начального значения. Так что это будет работать: (reduce + 0 (eduction my-identity (range 5)))

  10. Теперь, если вы запустите выше reduce как я полагаю, вы увидите что-то очень интересное. Он печатает 10. Даже если ваше образование напечатано ранее (0 0 1 1 2 2 3 3 4 4) (что, если вы сложите вместе 20). Что тут происходит?

  11. Как говорилось ранее, у вашего датчика есть недостаток. Это не работает должным образом. Проблема в том, что вы называете rf а затем вызовите его второй раз в функции arity 2. В clojure материал не является изменяемым, если только он не является внутренне изменяемым для целей оптимизации:). Здесь проблема в том, что иногда clojure использует мутацию, и вы получаете дубликаты, даже если вы никогда не фиксируете должным образом результат вашего первого вызова (rf) в вашей функции arity 2 (в качестве аргумента println).

Давайте исправим вашу функцию, но оставим второй rf позвоните туда:

  (defn my-identity2 [rf]
    (fn
      ([]
       (println "Arity 0.")
       (rf))
      ([result]
       {:post [(do (println "Arity 1 " %) true)]
        :pre  [(do (println "Arity 1 " result) true)]}
       (rf result))
      ([result input]
       {:post [(do (println "Arity 2 " %) true)]
        :pre  [(do (println "Arity 2 " result input) true)]}
       (rf (rf result input) input))))

Замечания:

  • Я переименовал xf в rf как отмечалось раньше.
  • Теперь мы можем видеть, что вы используете результат вас rf и передать его на второй вызов rf, Этот преобразователь не является преобразователем идентичности, но удваивает каждый элемент

Соблюдайте внимательно:

(transduce my-identity + (range 5));; => 10
(transduce my-identity2 + (range 5));; => 20

(count (into '() my-identity (range 200)));; => 200
(count (into  [] my-identity (range 200)));; => 400

(count (into '() my-identity2 (range 200)));; => 400
(count (into  [] my-identity2 (range 200)));; => 400

(eduction my-identity  (range 5));;=> (0 0 1 1 2 2 3 3 4 4)
(eduction my-identity2 (range 5));;=> (0 0 1 1 2 2 3 3 4 4)

(into '() my-identity  (range 5));;=> (4 3 2 1 0)
(into  [] my-identity  (range 5));;=> [0 0 1 1 2 2 3 3 4 4]
(into '() my-identity2 (range 5));;=> (4 4 3 3 2 2 1 1 0 0)


(reduce + 0 (eduction my-identity (range 5)));;=> 10
(reduce +   (sequence my-identity (range 5)));;=> 20

(reduce + 0 (eduction my-identity2 (range 5)));;=> 20
(reduce +   (sequence my-identity2 (range 5)));;=> 20

Чтобы ответить на ваши вопросы:

  1. eduction на самом деле не проходит nil как result аргумент, когда это уменьшается. Он получает ноль только при печати, который вызывает Iterable интерфейс.
  2. nil действительно происходит из TransformerIterator который является специальным классом, созданным для преобразователей. Этот класс также используется для sequence как вы заметили. Как утверждают документы:

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

Причина, по которой вы получаете nil как result аргумент в том, что итератор не имеет результирующей коллекции, которая содержит элементы, повторяемые до сих пор. Это просто проходит по каждому элементу. Ни одно государство не накапливается.

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

https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/TransformerIterator.java

Сделать CTRL+f и введите xf.invoke чтобы увидеть, как вызывается ваш датчик.

sequence Функция на самом деле не такая ленивая, как действительно ленивая последовательность, но я думаю, что это объясняет эту часть вашего вопроса:

Стремятся ли преобразователи Clojure?

sequence просто вычисляет результаты преобразователя постепенно. Ничего больше.

Наконец, правильная функция тождественности с некоторыми операторами отладки:

(defn my-identity-prop [xf]
  (fn
    ([]
     (println "Arity 0.")
     (xf))
    ([result]
     (let [r (xf result)]
       (println "my-identity(" result ") =" r)
       r))
    ([result input]
     (let [r (xf result input)]
       (println "my-idenity(" result "," input ") =" r)
       r))))
Другие вопросы по тегам