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 ответ
Много вопросов, давайте начнем с нескольких ответов:
- Да,
xf
==xform
это "преобразователь". - Ваш
my-identity
функция не компилируется. У вас есть параметр, а затем несколько других функций функции. Я полагаю, вы забыли(fn ...)
, Ваш аргумент к вашему преобразователю личности называется
xf
, Тем не менее, это обычно называетсяrf
, что означает "восстанавливающая функция". Теперь запутанная часть заключается в том, чтоxf
также сокращают функции (следовательно,comp
просто работает). Тем не менее, это сбивает с толку, что вы бы назвали этоxf
и ты должен это назватьrf
,Преобразователи обычно "создаются", так как они могут иметь состояние и / или передаваемые параметры. В вашем случае вам не нужно создавать его, так как он прост и не имеет состояния или даже параметра. Однако имейте в виду, что вы обычно заключаете свою функцию в другую
fn
возвращающая функция. Это означает, что вам нужно позвонить(my-identity)
вместо того, чтобы просто передать его какmy-identity
, Опять же, здесь все хорошо, просто немного неубедительно и, возможно, сбивает с толку.Давайте сначала продолжим и сделаем вид, что ваш
my-identity
преобразователь правильный (это не так, и я объясню позже, что происходит).eduction
относительно редко используется. Это создает "процесс". Т.е. вы можете запустить его снова и снова и увидеть результат. По сути, точно так же, как у вас есть списки или векторы, в которых хранятся ваши элементы, eduction будет "содержать" результат применения преобразователя. Обратите внимание, что для того, чтобы сделать что-либо, вам все еще нуженrf
(редукционная функция).В начале я думаю, что полезно думать о сокращении функций как о
conj
(или на самом делеconj!
) или в вашем случае+
,Ваш
eduction
печатает элементы, которые он производит, так как он реализуетIterable
который называетсяprintln
или ваш ответ. Он просто распечатывает каждый элемент, который вы добавляете в свой датчик с помощью вызова 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
Сделать 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))))