2010-06-09 12 views
89

So che cons restituisce un seq e conj restituisce una raccolta. So anche che conj "aggiunge" l'elemento all'estremità ottimale della raccolta e cons "aggiunge" sempre l'elemento in primo piano. Questo esempio illustra entrambi questi punti:Clojure: cons (seq) vs. conj (lista)

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) 

Per vettori, mappe e imposta queste differenze hanno senso per me. Tuttavia, per gli elenchi sembrano identici.

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) 

Ci sono esempi che utilizzano gli elenchi in cui conj vs. cons mostrare comportamenti diversi, o sono veramente intercambiabili? In modo diverso, c'è un esempio in cui una lista e un seq non possono essere usati in modo equivalente?

risposta

140

Una differenza è che conj accetta qualsiasi numero di argomenti da inserire in una collezione, mentre cons vuole solo un:

(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 

Un'altra differenza è nella classe del valore di ritorno:

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

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

Si noti che questi non sono realmente intercambiabili; in particolare, clojure.lang.Cons non implementa clojure.lang.Counted, quindi un count su di esso non è più un'operazione a tempo costante (in questo caso si ridurrebbe probabilmente a 1 + 3 - il 1 deriva da traversal lineare sul primo elemento, il 3 viene da (next (cons 4 '(1 2 3)) è un PersistentList e quindi Counted).

L'intenzione dietro i nomi è, credo, che cons significa cons (truct un ss) , mentre conj significa conj (oin un elemento su una collezione). Il seq viene costruito da cons inizia con l'elemento passato come primo argomento e ha come parte next/rest la cosa risultante dall'applicazione di seq al secondo argomento; come mostrato sopra, il tutto è della classe clojure.lang.Cons. Al contrario, conj restituisce sempre una raccolta di circa lo stesso tipo della raccolta passata. (Approssimativamente, perché un PersistentArrayMap sarà trasformato in un PersistentHashMap appena cresce oltre 9 voci.)


Tradizionalmente, nel mondo Lisp, cons cons (tructs una coppia), così Clojure parte dalla tradizione Lisp per avere la sua funzione cons costruire un seq che non ha un tradizionale cdr. L'uso generalizzato di cons per indicare "costruire un record di un tipo o altro per contenere insieme un numero di valori" è attualmente onnipresente nello studio dei linguaggi di programmazione e della loro implementazione; questo è ciò che si intende quando viene menzionato "evitando il consing".

+1

Che articolo fantastico! Non ero a conoscenza del fatto che esistesse un tipo di contro. Molto bene! –

+0

Grazie. Sono felice di sentirlo. :-) –

+2

Per inciso, come caso speciale, '(cons foo nil)' restituisce un singleton 'PersistentList' (e allo stesso modo per' conj'). –

9

La mia comprensione è che ciò che dici è vero: conj su una lista equivale a cons in una lista.

Si può pensare a conj come un'operazione di "inserimento da qualche parte" e come un'operazione di "inserimento in testa". In una lista, è più logico inserire in testa, quindi conj e contro sono equivalenti in questo caso.

8

Un'altra differenza è che poiché conj prende una sequenza come primo argomento, gioca bene con alter quando si aggiorna un ref a qualche sequenza:

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

Questo fa sostanzialmente (conj a-sequence-ref an-item) in modo thread-safe. Questo non funzionerebbe con cons. Vedi il capitolo su Concurrency in Programming Clojure di Stu Halloway per maggiori informazioni.

1

Un'altra differenza è il comportamento dell'elenco?

(list? (conj() 1)) ;=> true 
(list? (cons 1())) ; => false 
+4

cons restituisce sempre una sequenza che conj restituisce lo stesso tipo di quello fornito –