Clojure: cons (seq) vs conj (liste)

98

Je sais que consrenvoie un seq et conjrenvoie une collection. Je sais aussi que conj«ajoute» l'article à l'extrémité optimale de la collection, et cons«ajoute» toujours l'article à l'avant. Cet exemple illustre ces deux points:

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)

Pour les vecteurs, les cartes et les ensembles, ces différences me semblent logiques. Cependant, pour les listes, elles semblent identiques.

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)

Existe-t-il des exemples utilisant des listes où conjvs consprésentent des comportements différents, ou sont-ils vraiment interchangeables? Formulé différemment, y a-t-il un exemple où une liste et une séquence ne peuvent pas être utilisées de manière équivalente?

dbyrne
la source

Réponses:

150

Une différence est qu'elle conjaccepte n'importe quel nombre d'arguments à insérer dans une collection, alors qu'elle consn'en prend qu'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

Une autre différence réside dans la classe de la valeur de retour:

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

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

Notez que ceux-ci ne sont pas vraiment interchangeables; en particulier, clojure.lang.Consne met pas en œuvre clojure.lang.Counted, donc a counton ce n'est plus une opération à temps constant (dans ce cas il se réduirait probablement à 1 + 3 - le 1 provient d'un parcours linéaire sur le premier élément, le 3 vient d' (next (cons 4 '(1 2 3))être a PersistentListet ainsi Counted).

L'intention derrière les noms est, je crois, cela conssignifie de contre (truire une suite) 1 , alors que cela conjsignifie de conj (joindre un élément à une collection). Le seqcours de construction par conscommence avec l'élément passé en premier argument et a pour next/ restpartie la chose résultant de l'application de seqla deuxième argument; comme affiché ci-dessus, le tout est de classe clojure.lang.Cons. En revanche, conjrenvoie toujours une collection à peu près du même type que la collection qui lui est passée. (En gros, parce que a PersistentArrayMapsera transformé en a PersistentHashMapdès qu'il dépassera 9 entrées.)


1 Traditionnellement, dans le monde Lisp, conscontre (truit une paire), Clojure s'écarte donc de la tradition Lisp en ayant sa consfonction construire un seq qui n'a pas de traditionnel cdr. L'utilisation généralisée de conspour signifier "construire un enregistrement d'un type ou d'un autre pour contenir un certain nombre de valeurs ensemble" est actuellement omniprésente dans l'étude des langages de programmation et de leur implémentation; c'est ce que signifie «éviter de consommer».

Michał Marczyk
la source
1
Quel fantastique article! J'ignorais qu'il y avait un type contre. Bien joué!
Daniel Yankowsky
Merci. Heureuse d'entendre cela. :-)
Michał Marczyk
2
Incidemment, comme cas particulier, (cons foo nil)renvoie un singleton PersistentList(et de même pour conj).
Michał Marczyk
1
Encore une superbe explication. Vous êtes vraiment un jedi clojure!
dbyrne
1
D'après mon expérience, traiter les listes comme des listes et non comme des séquences est important lorsque la performance compte.
cgrand
11

Je crois comprendre que ce que vous dites est vrai: conj sur une liste équivaut à contre sur une liste.

Vous pouvez considérer conj comme une opération "insérer quelque part", et par contre comme une opération "insérer en tête". Sur une liste, il est plus logique d'insérer en tête, donc conj et cons sont équivalents dans ce cas.

Daniel Yankowsky
la source
8

Une autre différence est que parce que conjprend une séquence comme premier argument, il joue bien avec alterlors de la mise à jour refd'une séquence:

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

Cela se fait essentiellement (conj a-sequence-ref an-item)d'une manière thread-safe. Cela ne fonctionnerait pas avec cons. Voir le chapitre sur la concurrence dans la programmation de Clojure par Stu Halloway pour plus d'informations.

user323818
la source
2

Une autre différence est le comportement de la liste?

(list? (conj () 1)) ;=> true
(list? (cons 1 ())) ; => false
FredAKA
la source
4
cons renvoie toujours une séquence qui conj renvoie le même type que celle fournie
Ning Sun