conj clojure stack-overflow cons

clojure cons vs conj con perezoso-seq



conj vector clojure (1)

(conj collection item) agrega item a la collection . Para hacer eso, necesita realizar la collection . (Explicaré por qué a continuación.) Por lo tanto, la llamada recursiva ocurre de inmediato, en lugar de posponerse.

(cons item collection) crea una secuencia que comienza con el item , seguido de todo en la collection . Significativamente, no es necesario realizar la collection . Entonces, la llamada recursiva se aplazará (debido al uso de lazy-seq ) hasta que alguien intente obtener la cola de la secuencia resultante.

Explicaré cómo funciona esto internamente:

cons devuelve realmente un objeto clojure.lang.Cons , que es de lo que están hechas las secuencias perezosas. conj devuelve el mismo tipo de colección que la pasa (ya sea una lista, un vector o cualquier otra cosa). conj hace esto usando una llamada de método polimórfico Java en la colección en sí. (Vea la línea 524 de clojure/src/jvm/clojure/lang/RT.java ).

¿Qué sucede cuando ocurre esa llamada al método Java en el objeto clojure.lang.LazySeq que devuelve lazy-seq ? (La forma en que los objetos LazySeq y LazySeq funcionan juntos para formar secuencias perezosas se LazySeq más adelante). Mire la línea 98 de clojure/src/jvm/clojure/lang/LazySeq.java . Observe que llama a un método llamado seq . Esto es lo que se da cuenta del valor de LazySeq (salte a la línea 55 para más detalles).

Entonces, podrías decir que el conj necesita saber exactamente qué tipo de colección lo pasaste, pero los cons no lo hacen. cons solo requiere que el argumento "colección" sea un ISeq .

Tenga en cuenta que los objetos Cons en Clojure son diferentes de las "células cons" en otros Lisps: en la mayoría de Lisps, un "contra" es simplemente un objeto que contiene 2 punteros a otros objetos arbitrarios. De modo que puede usar células de cons para construir árboles, y así sucesivamente. Un Clojure Cons toma un Object arbitrario como cabeza, y un ISeq como cola. Dado que los propios Cons implementan ISeq , puede construir secuencias a partir de objetos Cons , pero también pueden indicar vectores, listas, etc. (Tenga en cuenta que una "lista" en Clojure es un tipo especial (Lista PersistentList ) y no es construido a partir de objetos Cons .) clojure.lang.LazySeq también implementa ISeq , por lo que se puede usar como la cola ("cdr" en Lisps) de un Cons . Un LazySeq contiene una referencia a algún código que evalúa a un ISeq de algún tipo, pero en realidad no evalúa ese código hasta que sea necesario, y después de que evalúa el código, almacena en caché el ISeq devuelto y lo delega.

... ¿todo esto empieza a tener sentido? ¿Tienes la idea de cómo funcionan las secuencias perezosas? Básicamente, comienzas con un LazySeq . Cuando se realiza LazySeq , se evalúa como un Cons , que apunta a otro LazySeq . Cuando ese se realiza ... entiendes la idea. Entonces obtienes una cadena de objetos de LazySeq , cada tenencia (y delegación) de un Cons .

Acerca de la diferencia entre "conses" y "listas" en Clojure, "listas" (objetos PersistentList ) contienen un campo de "longitud" en caché, por lo que pueden responder para count en O (1) vez. Esto no funcionaría en otros Lisps, porque en la mayoría de Lisps, las "listas" son mutables. Pero en Clojure son inmutables, por lo que el almacenamiento en caché funciona.

Cons objetos Cons en Clojure no tienen una longitud de caché; si lo hicieran, ¿cómo podrían usarse para implementar secuencias perezosas (e incluso infinitas)? Si intentas count el count de Cons , solo count en su cola, y luego incrementa el resultado en 1.

¿Por qué los inconvenientes funcionan en este contexto con lazy-seq, pero conj no?

Esto funciona:

(defn compound-interest [p i] (cons p (lazy-seq (compound-interest (* p (+ 1 i)) i))))

Esto no (da una excepción de desbordamiento de pila [1]):

(defn compound-interest2 [p i] (conj (lazy-seq (compound-interest2 (* p (+ 1 i)) i)) p))

[1] ¡Oh, ya! Hacer una pregunta que involucra un desbordamiento de pila en stackoverflow.