collections - Clojure: contras(seq) vs. conj(lista)
sequence (4)
Sé que los cons
devuelven un seq y conj
devuelve una colección. También sé que conj
"agrega" el artículo al final óptimo de la colección, y los cons
siempre "agregan" el elemento 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 algún ejemplo de uso de listas en las que conj
cons
exhiben comportamientos diferentes o son realmente intercambiables? Expresado de manera diferente, ¿hay algún ejemplo en el que una lista y un seq no puedan usarse de manera equivalente?
Entiendo que lo que dices es verdad: conj en una lista es equivalente a los contras en una lista.
Puede pensar que conj es una operación de "insertar en alguna parte" y es una operación de "insertar en la cabeza". En una lista, es más lógico insertar en la cabecera, por lo que conj y contras son equivalentes en este caso.
Otra diferencia es el comportamiento de la lista?
(list? (conj () 1)) ;=> true
(list? (cons 1 ())) ; => false
Otra diferencia es que, debido a que conj
toma una secuencia como primer argumento, juega muy bien con alter
al actualizar una ref
a alguna secuencia:
(dosync (alter a-sequence-ref conj an-item))
Esto básicamente hace (conj a-sequence-ref an-item)
de una manera segura para subprocesos. Esto no funcionaría con los cons
. Vea el capítulo sobre Concurrencia en la programación de Clojure por Stu Halloway para más información.
Una diferencia es que conj
acepta cualquier número de argumentos para insertar en una colección, mientras que cons
solo una:
(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.Cons
no implementa clojure.lang.Counted
, por lo que count
con él ya no es una operación de tiempo constante (en este caso, probablemente se reduciría a 1 + 3 - el 1 proviene de un cruce lineal sobre el primer elemento, el 3 viene de (next (cons 4 ''(1 2 3))
siendo una PersistentList
y por lo tanto Counted
).
La intención detrás de los nombres es, creo, que los cons
significan contras (truct a seq) 1 , mientras que conj
significa conj (oin un ítem en una colección). El seq
se construye por cons
comienza con el elemento pasado como su primer argumento y tiene como su parte next
/ rest
lo que resulta de la aplicación de seq
al segundo argumento; como se muestra arriba, todo es de la clase clojure.lang.Cons
. Por el contrario, conj
siempre devuelve una colección de aproximadamente el mismo tipo que la colección que se le pasó. (Aproximadamente, porque un PersistentArrayMap
se convertirá en un PersistentHashMap
tan pronto como crezca más allá de 9 entradas).
1 Tradicionalmente, en el mundo de Lisp, cons
cons (tructuras un par), entonces Clojure se aparta de la tradición de Lisp al hacer que su función cons
construya un seq que no tiene un cdr
tradicional. El uso generalizado de cons
para significar "construir un registro de algún tipo u otro para mantener una cantidad de valores en conjunto" es actualmente omnipresente en el estudio de los lenguajes de programación y su implementación; eso es lo que significa cuando se menciona "evitar el consing".