Clojure: cons (seq) frente a conj (lista)

98

Sé que consdevuelve una secuencia y conjdevuelve una colección. También sé que conj"agrega" el artículo al final óptimo de la colección, y conssiempre "agrega" el artículo al frente. Este ejemplo ilustra ambos puntos:

user=> (conj [1 2 3] 4) ; returns a collection
[1 2 3 4]
user=> (cons 4 [1 2 3]) ; returns a seq
(4 1 2 3)

Para vectores, mapas y conjuntos, estas diferencias tienen sentido para mí. Sin embargo, para las listas parecen idénticas.

user=> (conj (list 3 2 1) 4) ; returns a list
(4 3 2 1)
user=> (cons 4 (list 3 2 1)) ; returns a seq
(4 3 2 1)

¿Hay ejemplos de listas en las que conjvs. consexhiben comportamientos diferentes, o son realmente intercambiables? Expresado de manera diferente, ¿hay algún ejemplo en el que una lista y una secuencia no se puedan usar de manera equivalente?

dbyrne
fuente

Respuestas:

150

Una diferencia es que conjacepta cualquier número de argumentos para insertar en una colección, mientras que conssolo toma uno:

(conj '(1 2 3) 4 5 6)
; => (6 5 4 1 2 3)

(cons 4 5 6 '(1 2 3))
; => IllegalArgumentException due to wrong arity

Otra diferencia está en la clase del valor de retorno:

(class (conj '(1 2 3) 4))
; => clojure.lang.PersistentList

(class (cons 4 '(1 2 3))
; => clojure.lang.Cons

Tenga en cuenta que estos no son realmente intercambiables; en particular, clojure.lang.Consno implementa clojure.lang.Counted, por lo que un countsobre que ya no es una operación de tiempo constante (en este caso probablemente reduciría a 1 + 3 - 1 al proviene de recorrido lineal en el primer elemento, el 3 viene de (next (cons 4 '(1 2 3))ser PersistentListy así Counted).

La intención detrás de los nombres es, creo, que conssignifica construir una seq. 1 , mientras que conjsignifica conjugar un elemento en una colección. El seqser construido por conscomienza con el elemento pasado como primer argumento y tiene como su next/ restparte lo resultante de la aplicación de seqal segundo argumento; como se muestra arriba, todo es de clase clojure.lang.Cons. Por el contrario, conjsiempre devuelve una colección de aproximadamente el mismo tipo que la colección que se le pasó. (Aproximadamente, porque a PersistentArrayMapse convertirá en a PersistentHashMaptan pronto como supere las 9 entradas).


1 Tradicionalmente, en el mundo Lisp, conscontras (construye un par), por lo que Clojure se aparta de la tradición Lisp al hacer que su consfunción construya un seq que no tiene un tradicional cdr. El uso generalizado de conspara significar "construir un registro de un tipo u otro para mantener juntos un número de valores" es actualmente omnipresente en el estudio de los lenguajes de programación y su implementación; eso es lo que se quiere decir cuando se menciona "evitar la consulta".

Michał Marczyk
fuente
1
¡Qué artículo tan fantástico! No sabía que había un tipo de Contras. ¡Bien hecho!
Daniel Yankowsky
Gracias. Feliz de escuchar que. :-)
Michał Marczyk
2
Por cierto, como caso especial, (cons foo nil)devuelve un singleton PersistentList(y lo mismo para conj).
Michał Marczyk
1
Otra magnífica explicación. ¡Realmente eres un jedi clojure!
dbyrne
1
En mi experiencia, tratar las listas como listas y no como seqs es importante cuando el rendimiento importa.
cgrand
11

Tengo entendido que lo que dice es cierto: conj en una lista es equivalente a contras en una lista.

Puede pensar en conj como una operación de "insertar en algún lugar" y en contras como una operación de "insertar en la cabeza". En una lista, es más lógico insertar al principio, por lo que conj y cons son equivalentes en este caso.

Daniel Yankowsky
fuente
8

Otra diferencia es que debido a que conjtoma una secuencia como primer argumento, funciona bien altercuando se actualiza refa alguna secuencia:

(dosync (alter a-sequence-ref conj an-item))

Esto básicamente lo hace (conj a-sequence-ref an-item)de manera segura para subprocesos. Esto no funcionaría con cons. Consulte el capítulo sobre Concurrencia en la programación de Clojure de Stu Halloway para obtener más información.

user323818
fuente
2

¿Otra diferencia es el comportamiento de la lista?

(list? (conj () 1)) ;=> true
(list? (cons 1 ())) ; => false
FredAKA
fuente
4
cons siempre devuelve una secuencia que conj devuelve el mismo tipo de la proporcionada
Ning Sun
-1

Hay funciones dedicadas en la biblioteca de Tupelo para agregar agregar o anteponer valores a cualquier colección secuencial:

(append [1 2] 3  )   ;=> [1 2 3  ]
(append [1 2] 3 4)   ;=> [1 2 3 4]

(prepend   3 [2 1])  ;=> [  3 2 1]
(prepend 4 3 [2 1])  ;=> [4 3 2 1]
Alan Thompson
fuente