Clojure: cons (seq) vs. conj (list)

Posted

技术标签:

【中文标题】Clojure: cons (seq) vs. conj (list)【英文标题】: 【发布时间】:2011-03-01 19:21:44 【问题描述】:

我知道cons 返回一个序列,conj 返回一个集合。我也知道conj 将项目“添加”到集合的最佳末尾,而cons 总是将项目“添加”到前面。这个例子说明了这两点:

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)

对于矢量、地图和集合,这些差异对我来说很有意义。但是,对于列表,它们似乎相同。

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)

是否有任何使用列表的示例,其中 conjcons 表现出不同的行为,或者它们是否真的可以互换?换个说法,有没有一个 list 和 seq 不能等价使用的例子?

【问题讨论】:

【参考方案1】:

一个区别是conj 接受任意数量的参数以插入到集合中,而cons 只接受一个:

(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

另一个区别是返回值的类:

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

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

请注意,这些并不是真正可以互换的;特别是,clojure.lang.Cons 没有实现clojure.lang.Counted,因此在它上面的count 不再是一个常数时间操作(在这种情况下,它可能会减少到 1 + 3——1 来自第一个的线性遍历元素,3 来自 (next (cons 4 '(1 2 3))PersistentList,因此是 Counted)。

我相信,这些名称背后的意图是,cons 意味着 cons(truct a seq)1,而 conj 意味着 conj(将一个项目放入一个集合中)。由cons 构造的seq 以作为其第一个参数传递的元素开始,并将next / rest 部分作为将seq 应用于第二个参数的结果;如上所示,整个事物属于clojure.lang.Cons 类。相反,conj 总是返回一个与传递给它的集合大致相同类型的集合。 (粗略地说,因为PersistentArrayMap 将在超过 9 个条目后立即变成PersistentHashMap。)


1 传统上,在 Lisp 世界中,cons cons(tructs a pair),因此 Clojure 背离了 Lisp 传统,它的 cons 函数构造了一个没有一个传统的cdrcons 的广义用法表示“构建某种类型的记录以将多个值保存在一起”目前在编程语言及其实现的研究中无处不在。这就是提到“避免欺骗”时的意思。

【讨论】:

写得真棒!我不知道有一个 Cons 类型。干得好! 谢谢。高兴听到。 :-) 顺便说一下,作为一种特殊情况,(cons foo nil) 返回一个单例PersistentList(同样适用于conj)。 另一个极好的解释。你真的是一个clojure绝地! 根据我的经验,当性能很重要时,将列表视为列表而不是序列是很重要的。【参考方案2】:

我的理解是,你说的是真的:列表上的 conj 等同于列表上的 cons。

您可以将 conj 视为“插入某处”操作,将 cons 视为“插入头部”操作。在列表中,最符合逻辑的做法是在头部插入,因此 conj 和 cons 在这种情况下是等价的。

【讨论】:

【参考方案3】:

另一个区别是,因为conj 将序列作为第一个参数,所以在将ref 更新为某个序列时,它可以很好地与alter 配合使用:

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

这基本上以线程安全的方式执行(conj a-sequence-ref an-item)。这不适用于cons。有关更多信息,请参阅 Stu Halloway 的 Programming Clojure 中关于并发的章节。

【讨论】:

【参考方案4】:

另一个区别是列表的行为?

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

【讨论】:

cons 总是返回一个序列,其中 conj 返回与提供的序列相同的类型【参考方案5】:

有dedicated functions in the Tupelo Library 可以向任何顺序集合添加附加值或前置值:

(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]

【讨论】:

以上是关于Clojure: cons (seq) vs. conj (list)的主要内容,如果未能解决你的问题,请参考以下文章

如何具体化 Prolog 的回溯状态以执行与 Clojure 中的“lazy seq”相同的任务?

Clojure / seeseaw.core / table lazy-seq检索失败

Clojure测试空白,非收集安全

Clojure - 如何返回序列?

clojure 中交错的扩展

clojure - strng-concat with group by in maps of maps