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)) be всегда равны по крайней мере в таком маленьком примере без каких-либо преобразователей состояния. Это глупо, что это не так, или я ошибаюсь?

Хорошо, тогда я попробовал (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

Много вопросов, пусть сначала начнется с нескольких анверов:

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

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

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

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

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

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

  • Ваш вызов (reduce + (eduction my-identity (range 5))) не работает поскольку eduction (объект, построенный в eduction), реализует только IReduceInit. IReduceInit, поскольку его название предполагает, что требует начального стоимость. Итак, это сработает: (reduce + 0 (eduction my-identity (range 5)))

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

  • Как уже говорилось, ваш преобразователь имеет недостаток. Это не работает должным образом. проблема в том, что вы вызываете свой 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

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

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

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

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

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

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

Сделайте a 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))))