Je sais que cons
renvoie une séquence et conj
renvoie une collection. Je sais également que conj
"ajoute" l'élément à la fin optimale de la collection, et cons
"ajoute" toujours l'élément à 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 ont un sens pour moi. 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 d'utilisation de listes où conj
et cons
présentent des comportements différents, ou sont-ils vraiment interchangeables? Exprimé différemment, existe-t-il un exemple où une liste et une séquence ne peuvent pas être utilisées de manière équivalente?
Une différence est que conj
accepte n'importe quel nombre d'arguments à insérer dans une collection, tandis que cons
n'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.Cons
n'implémente pas clojure.lang.Counted
, donc un count
dessus n'est plus une opération à temps constant (dans ce cas, il serait probablement réduit à 1 + 3 - le 1 provient du parcours linéaire sur le premier élément, le 3 provient de (next (cons 4 '(1 2 3))
étant un PersistentList
et donc Counted
).
L'intention derrière les noms est, je crois, que cons
signifie contre (truct a seq)1, tandis que conj
signifie conj (insérer un élément dans une collection). seq
en cours de construction par cons
commence par l'élément passé comme premier argument et a pour partie next
/rest
la chose résultant de l'application de seq
au deuxième argument; comme indiqué ci-dessus, le tout est de classe clojure.lang.Cons
. En revanche, conj
renvoie toujours une collection à peu près du même type que la collection qui lui est passée. (En gros, parce qu'un PersistentArrayMap
sera transformé en PersistentHashMap
dès qu'il dépassera 9 entrées.)
1 Traditionnellement, dans le monde LISP, cons
contre (traite une paire), donc Clojure s'écarte de la tradition LISP en ayant sa fonction cons
construit une séquence qui n'a pas de cdr
. L'utilisation généralisée de cons
pour signifier "construire un enregistrement d'un type ou d'un autre pour contenir un certain nombre de valeurs" est actuellement omniprésente dans l'étude des langages de programmation et leur implémentation; c'est ce que l'on veut dire quand "éviter de contrer" est mentionné.
Ma compréhension est 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 de l'insérer en tête, donc conj et contre sont équivalents dans ce cas.
Une autre différence est que parce que conj
prend une séquence comme premier argument, il joue bien avec alter
lors de la mise à jour d'un ref
vers une séquence:
(dosync (alter a-sequence-ref conj an-item))
Cela 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 Programmation Clojure par Stu Halloway pour plus d'informations.
Une autre différence est le comportement de la liste?
(list? (conj () 1)) ;=> true
(list? (cons 1 ())) ; => false