clojure transducer

Comportamiento de transductores Clojure



transducer (1)

Muchas preguntas, primero comencemos con algunas torres:

  1. Sí, xf == xform es un "transductor".
  2. Tu función my-identity no se compila. Usted tiene un parámetro y luego otras aries múltiples de la función. Creo que te olvidaste de un (fn ...) .
  3. Su argumento para su transductor de identidad se llama xf . Sin embargo, esto generalmente se llama rf , que significa "función reductora". Ahora la parte confusa es que los xf también reducen funciones (por lo tanto, comp solo funciona). Sin embargo, es confuso que lo llames xf y deberías llamarlo rf .

  4. Los transductores generalmente están "construidos" ya que pueden ser con estado y / o se pasan parámetros. En su caso, no necesita construirlo ya que es simple y no tiene estado o incluso un parámetro. Sin embargo, tenga en cuenta que, por lo general, ajustaría su función a otra función de retorno fn . Esto significa que tendrá que llamar a (my-identity) lugar de simplemente pasarlo como my-identity . Nuevamente, está bien aquí, solo un poco inconcebible y posiblemente confuso.

  5. Primero continuemos y pretendamos que su transductor de my-identity es correcto (no lo es, y explicaré más adelante lo que está sucediendo).

  6. eduction es relativamente poco utilizada. Crea un "proceso". Es decir, puede ejecutarlo una y otra vez y ver el resultado. Básicamente, al igual que tiene listas o vectores que contienen sus elementos, la educción "retendrá" el resultado del transductor aplicado. Tenga en cuenta que para hacer realmente algo, todavía necesita un rf (función reductora).

  7. Al principio, creo que es útil pensar en reducir funciones como conj (o en realidad conj! ) O en tu caso + .

  8. Tu eduction imprime los elementos que produce ya que implementa Iterable que es invocado por println o tu REPL. Simplemente imprime cada elemento que agrega en su transductor con la llamada arity 2.

  9. Tu llamada a (reduce + (eduction my-identity (range 5))) no funciona porque Eduction (el objeto que se está construyendo en eduction ) solo implementa IReduceInit . IReduceInit como su nombre sugiere requiere un valor inicial. Entonces esto funcionará: (reduce + 0 (eduction my-identity (range 5)))

  10. Ahora, si ejecutas lo anterior, reduce como te sugiero que verás algo muy interesante. Imprime 10. Aunque su educción se imprimió anteriormente (0 0 1 1 2 2 3 3 4 4) (que si sumas juntas es 20). ¿Que está pasando aqui?

  11. Como se dijo anteriormente, su transductor tiene un defecto. No funciona correctamente El problema es que llama a su rf y luego lo llama por segunda vez en su función arity 2. En Clojure, las cosas no son mutables, a menos que de alguna manera sean mutables internamente para fines de optimización :). Aquí el problema es que a veces Clojure usa mutación y obtienes duplicados aunque nunca captures adecuadamente el resultado de la primera vez que llamas (rf) en tu función arity 2 (como el argumento de tu println ).

Arreglemos su función pero dejemos la segunda llamada de rf allí :

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

Nota:

  • Cambié el nombre de xf a rf como earier.
  • Ahora podemos ver que usa el resultado de su rf y lo pasa a la segunda llamada de rf . Este transductor no es un transductor de identidad, sino que duplica cada elemento

Observe cuidadosamente:

(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

Para responder a sus preguntas:

  1. eduction realidad no pasa nil como argumento del result cuando se reduce . Solo se vuelve nulo cuando se imprime lo que llama a la interfaz Iterable .
  2. El nil realmente viene de TransformerIterator que es una clase especial creada para transductores. Esta clase también se usa para sequence como habrás notado. Como dice el documento:

Los elementos de secuencia resultantes se computan incrementalmente. Estas secuencias consumirán la entrada de forma incremental según sea necesario y realizarán completamente las operaciones intermedias. Este comportamiento difiere de las operaciones equivalentes en secuencias perezosas.

La razón por la que recibe nil como argumento result es porque un iterador no tiene una colección resultante que contiene los elementos iterados hasta el momento. Simplemente repasa cada elemento. Ningún estado se está acumulando.

Puede ver la función de reducción que usa TransformerIterator como clase interna aquí:

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

Haga un CTRL+f e ingrese xf.invoke para ver cómo se llama su transductor.

La función de sequence no es tan vaga como una secuencia realmente floja, pero creo que esto explica esta parte de tu pregunta:

¿Los transductores Clojure están ansiosos?

sequence simplemente calcula los resultados de un transductor de forma incremental. Nada más.

Por último, una función de identidad adecuada con algunas sentencias de depuración:

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

Con el nuevo clojure 1.7 decidí entender dónde puedo usar transductores. Entiendo qué beneficio pueden dar, pero no puedo encontrar ejemplos normales de escritura de transductores personalizados con explicación.

Ok, traté de probar lo que está sucediendo. Abrí la documentación de clojure. Y hay ejemplos que usan xf como argumento. Primero: ¿qué significa este xf o xfrom? Esto produjo un transductor de identidad.

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

Tomé la denominación de variables [result input] del ejemplo de documentación. Pensé que era como en la función de reducción donde el result es una parte reducida y la input es un nuevo elemento de colección.

Entonces cuando hago (transduce my-identity + (range 5)) obtuve el resultado 10 lo que esperaba. Luego leo sobre la eduction , pero no puedo entender qué es. De todos modos, hice (eduction my-identity (range 5)) y obtuve:

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

Todos los artículos se duplicaron porque llamé a xf en la declaración println . ¿Por qué se duplicó cada artículo dos veces? ¿Por qué tengo nada? ¿Siempre me daré cuenta mientras hago una educción? ¿Puedo transmitir sobre este comportamiento?

De todos modos lo hice

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

Ok, el resultado es una Eduction que NO es reducible, pero se imprime como una lista. ¿Por qué no es reducible? Cuando (doc eduction) entiendo eso

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

¿No debería (transduce xform f coll) y (reduce f (eduction xfrom coll)) ser el mismo?

hice

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

Por supuesto que tengo 20 debido a los duplicados. De nuevo, pensé que debería ser que (transduce xform f coll) y (reduce f (sequence xfrom coll)) siempre sean iguales al menos en un ejemplo tan pequeño sin ningún transductor con estado. ¿Esto es estúpido que no lo son, o estoy equivocado?

Ok, entonces probé (type (sequence my-identity (range 5))) y obtenga clojure.lang.LazySeq. Pensé que era flojo, pero cuando traté de tomar el first elemento, clojure calculó toda la secuencia a la vez.

Entonces mi resumen:

1) ¿Qué significa xf o xform?

2) ¿Por qué obtengo nil como result un argumento mientras eduction o sequence ?

3) ¿Siempre podría estar seguro de que será nil mientras eduction o sequence ?

4) ¿Qué es la eduction y cuál es la idea idiomática que no es reducible? O si lo es, ¿cómo puedo reducirlo?

5) ¿Por qué tengo efectos secundarios durante la sequence o la eduction ?

6) ¿Puedo crear secuencias vagas reales con transductores?