2012-09-12 10 views
22

¿Por qué las contras funcionan en este contexto con lazy-seq, pero conj no?clojure cons vs conj con lazy-seq

Esto funciona:

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

Esto no lo hace (que da un desbordamiento de pila [1] excepción):

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

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

+0

Para otros que vienen aquí: mi respuesta a continuación es muy detallada (quizás demasiado detallada) y puede ser confuso. Permítanme repetir el punto principal aquí: la semántica de 'conj' requiere que varíe su comportamiento en función del tipo de colección. Eso requiere el uso de una llamada al método (polimórfico) en el objeto de colección. Un 'LazySeq' maneja esa llamada de método delegando en su valor interno, que requiere realizar ese valor interno. Por el contrario, la semántica de los contras no requiere que invoque ningún método en la colección; solo tiene que almacenarlo en un campo de un objeto 'Contras'. –

+0

Los últimos tres párrafos de la respuesta son particularmente útiles. Algunas personas pueden querer leerlas primero. – Mars

Respuesta

38

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

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

Voy a explicar cómo funciona internamente:

cons realidad devuelve un objeto clojure.lang.Cons, que es lo secuencias perezosos están hechos. conj devuelve el mismo tipo de colección que la pasa (ya sea una lista, un vector o cualquier otra cosa). conj hace esto utilizando una llamada de método polimórfico Java en la colección en sí. (Consulte line 524 of clojure/src/jvm/clojure/lang/RT.java.)

¿Qué sucede cuando ocurre esa llamada al método Java en el objeto clojure.lang.LazySeq que se devuelve por lazy-seq? (La forma en que los objetos Cons y LazySeq trabajan juntos para formar secuencias perezosas será más clara a continuación). Consulte line 98 of 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 line 55 para obtener más información).

Por lo tanto, podría decir que conj necesita saber exactamente qué tipo de colección lo pasó, pero cons no lo hace. cons solo requiere que el argumento "recopilación" sea ISeq.

Tenga en cuenta que Cons objetos en Clojure son diferentes de "células cons" en otros Lisps - en la mayoría de Lisp, un "contras" es simplemente un objeto que tiene 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 Cons implementa ISeq, puede construir secuencias de objetos Cons, pero también pueden indicar vectores, listas, etc. (Tenga en cuenta que una "lista" en Clojure es un tipo especial (PersistentList), y es no construido a partir de objetos Cons) clojure.lang.LazySeqtambién implementa ISeq, por lo que se puede utilizar como la cola ("cdr" en Lisps) de un Cons.Un LazySeq contiene una referencia a algún código que evalúa 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, empiezas con LazySeq. Cuando se realiza el LazySeq, se evalúa como Cons, que apunta a otro LazySeq. Cuando ese se realiza ... entiendes la idea. Entonces obtienes una cadena de objetos LazySeq, cada tenencia (y delegando a) un Cons.

Acerca de la diferencia entre "conses" y "listas" en Clojure, "listas" (objetos PersistentList) contienen un campo "longitud" en caché, por lo que pueden responder a count en O (1) hora. 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 en Clojure No tienen una longitud caché - si lo hicieran, ¿cómo podrían ser utilizados para implementar secuencias perezosos (e incluso infinitos)? Si intenta tomar el count de Cons, simplemente llama a count en la cola, y luego incrementa el resultado en 1.

+0

¿Quiere decir realizar el elemento :) –

+0

@Ivan: No - el orden de los argumentos para 'contra' y' conj' son opuestos. –

+0

Yeap lo siento, pensé que él escribió '(conj p (lazy-seq (compound-interest2 (* p (+ 1 i)) i)))'. –