Comportamiento de transductores Clojure
transducer (1)
Muchas preguntas, primero comencemos con algunas torres:
- Sí,
xf
==xform
es un "transductor". - 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 ...)
. Su argumento para su transductor de identidad se llama
xf
. Sin embargo, esto generalmente se llamarf
, que significa "función reductora". Ahora la parte confusa es que losxf
también reducen funciones (por lo tanto,comp
solo funciona). Sin embargo, es confuso que lo llamesxf
y deberías llamarlorf
.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 comomy-identity
. Nuevamente, está bien aquí, solo un poco inconcebible y posiblemente confuso.Primero continuemos y pretendamos que su transductor de
my-identity
es correcto (no lo es, y explicaré más adelante lo que está sucediendo).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 unrf
(función reductora).Al principio, creo que es útil pensar en reducir funciones como
conj
(o en realidadconj!
) O en tu caso+
.Tu
eduction
imprime los elementos que produce ya que implementaIterable
que es invocado porprintln
o tu REPL. Simplemente imprime cada elemento que agrega en su transductor con la llamada arity 2.Tu llamada a
(reduce + (eduction my-identity (range 5)))
no funciona porqueEduction
(el objeto que se está construyendo eneduction
) solo implementaIReduceInit
.IReduceInit
como su nombre sugiere requiere un valor inicial. Entonces esto funcionará:(reduce + 0 (eduction my-identity (range 5)))
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?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 tuprintln
).
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
arf
como earier. - Ahora podemos ver que usa el resultado de su
rf
y lo pasa a la segunda llamada derf
. 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:
-
eduction
realidad no pasanil
como argumento delresult
cuando se reduce . Solo se vuelve nulo cuando se imprime lo que llama a la interfazIterable
. - El
nil
realmente viene deTransformerIterator
que es una clase especial creada para transductores. Esta clase también se usa parasequence
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?