clojure:没有cons细胞

clojure: no cons cells

我听说 clojure 没有像大多数 lisp 语言一样的 cons 单元

这是否意味着 clojure 列表不会以空列表结尾?

谁能解释一下这到底是什么意思?

根据this page from clojure.org

cons, first and rest manipulate sequence abstractions, not concrete cons cells

Clojure 列表不以空列表结尾,它们不是传统的缺点单元格。它们是实现排序的数据结构。 This page on programming to abstractions 解释了 Clojure 对 "seqable" 结构的方法,包括列表:

In general, programming to abstractions gives you power by letting you use libraries of functions on different data structure regardless of how those data structures are implemented.

所以 Clojure 列表就像 cons 单元,因为它们实现了 consfirstrest,但这仅意味着它们共享一个公共接口。它们的底层实现不同,但它们都是 "seqable".

在 Common Lisp 中,列表是一系列 cons 单元格。每个 cons 单元有两个槽或指针,称为 "car" 和 "cdr"。汽车指向(或持有)某物——任何东西。 cdr 通常指向另一个 cons 单元格,或者 nilnil 算作列表的末尾。 Clojure 为您提供与其列表大致相同的功能,但底层表示不同。它确实有一个名为 Cons 的数据类型,但并非所有列表或给定列表的所有部分都是从 Cons 构建的。 (现在你应该阅读 jmargolisvt 的回答,如果你还没有的话。)[编辑:其他答案表明我在这里所说的关于 Clojure 中列表和 Conses 之间的关系是不正确的。人们可能会觉得它在非正式意义上是正确的 "list"-- 或者不是。]

另请注意,部分由于序列抽象思想,列表本身在 Clojure 中比在 Common Lisp 或 Scheme 中更不常见。但是,其他类型的序列很常见。

同样值得了解的是,在 Clojure 中,您不能假设打印出来时看起来像列表的东西实际上是一个列表。它可能是一个惰性序列,例如,被认为是一个列表。

以下是一些使用列表的潜在信息 Clojure 示例:

user=> (def foo (list 1))
#'user/foo
user=> foo
(1)
user=> (class foo)
clojure.lang.PersistentList
user=> (def bar (cons 2 foo))
#'user/bar
user=> bar
(2 1)
user=> (class bar)
clojure.lang.Cons

foobar 都被视为列表,即使 class returns 不同的数据类型。)

user=> (next bar)
(1)
user=> (rest bar)
(1)
user=> (class (next bar))
clojure.lang.PersistentList
user=> (class (rest bar))
clojure.lang.PersistentList
user=> (next foo)
nil
user=> (rest foo)
()
user=> (= nil ())
false
user=> (rest ())
()
user=> (rest nil)
()
user=> (next ())
nil
user=> (next nil)
nil

在 Common Lisp 中,您可以将一个对象构造到另一个对象而不是列表或 nil。结果是一个 "dotted list" (1 . 2),这是一个单独的 cons 单元,其中 cdr 指针指向另一个 cons 单元或 nil 以外的东西,就像在普通列表中一样。让我们在 Clojure 中尝试一下:

user=> (cons 1 2)
IllegalArgumentException Don't know how to create ISeq from: java.lang.Long  clojure.lang.RT.seqFrom (RT.java:528)

虽然我在这里,但与 Common Lisp 的另一个显着差异(其中 nil = () = false):

user=> (= nil false)
false
user=> (= () false)
false

但是,即使 nil 不是 false,您也可以像 false:

那样使用它
user=> (if nil "nil works like true" "nil works like false")
"nil works like false"

但是,您不能对空列表执行此操作:

user=> (if () "() works like true" "() works like false")
"() works like true"

(尽管有这些例子,总的来说,Clojure 比 Common Lisp 更简单和优雅,IMO。即使像我一样也喜欢 Common Lisp 的人也必须承认 Common Lisp 既不简单也不优雅.它自有一种美。)

Lisp 提供了一个原始的 cons 数据结构和一个符号。

参见 John McCarthy, Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I, 1960, Chapter 3, Recursive Functions of Symbolic Expressions

该章介绍:

  • 由原子组成的符号表达式和使用点符号编写的符号表达式对:( a . b )
  • 用于缩写某些符号表达式的列表符号(a b c)
  • 用于终止列表的原子符号nil
  • 原始函数 carcdrconseqatom
  • 其他几个函数:ffsubstequalnullcadrcaddrnull , append, among, pair, assoc, sublis, apply, eval, ...

在 Lisp 的早期,添加了改变 cons 单元的函数:rplaca(表示 替换汽车)和 rplacd(表示 替换 cdr)。参见LISP 1.5 Programmer's Manual by John McCarthy et al. from 1962。这些函数允许我们编写破坏性函数,也允许我们创建循环的基于 cons 的数据结构,如循环列表。

Common Lisp

通常 Lisp 方言实现了其中的大部分。 Common Lisp 也不例外,Common Lisp 标准对此功能进行了描述:Conses。使用上述函数的例子:

; pair two lists into a list of cons cells.
; the function pair is called pairlis in Common Lisp.
CL-USER 17 > (pairlis '(john mary eva) '(34 29 40))
((EVA . 40) (MARY . 29) (JOHN . 34))

; find a cons cell in a list of cons cells,
; based on the content of the car of those cons cells
CL-USER 18 > (assoc 'eva (pairlis '(john mary eva)
                                  '(34 29 40)))
(EVA . 40)

; create a tree out of cons cells and atoms
CL-USER 19 > (cons (cons 10 20) (cons 30 40))
((10 . 20) 30 . 40)

; a cons cell is not an atom
CL-USER 20 > (atom (cons 1 2))
NIL

; a cons cell is not nil
CL-USER 21 > (null (cons 1 2))
NIL

; substitute an item with a new one in a tree
CL-USER 22 > (subst 30                          ; new
                    'bar                        ; old
                    '((10 . 20) . (bar . 40)))  ; tree
((10 . 20) 30 . 40)   ; also written as  ((10 . 20) . (30 . 40))

; substitute several items in a tree, using an assoc list
; to describe the substitutions
CL-USER 23 > (sublis '((a . 10) (d . 40))      ; substitutions
                     '((a . b) . (c . d)))     ; tree
((10 . B) C . 40)

列表是符号表达式的一种特殊情况。它们通常不带点:

CL-USER 24 > '(a . (b . nil))
(A B)

Common Lisp 还支持 Lisp 1.5 的变异操作 rplacarplacd:

CL-USER 25 > (let ((c (cons 0 1)))              ; create a cons
               (print c)                        ; print it
               (print (rplaca c 'foo))          ; replace the car
               (print (rplacd c 'bar))          ; replace the cdr
               (print (eq c (rplaca c 'baz)))   ; identical ?
               (values))
(0 . 1)      ; the cons cell
(FOO . 1)    ; car replaced
(FOO . BAR)  ; cdr replaced
T            ; still the same object

Emacs Lisp

Emacs Lisp 也实现了上述功能:

ELISP> (sublis '((a . 10) (d . 40))                                             
               '((a . b) . (c . d)))
((10 . b) c . 40)

Clojure

Clojure 不支持 John McCarthy 描述的这些 符号表达式 。它没有缺点单元格,没有点符号,也不提供上述接口。例如 atom 在 Clojure 中意味着完全不同的东西。 cons 不创建 cons 单元格。列表不是由 cons 单元格组成的。

在 Clojure 中,点只是另一个符号:

user=> (count '(1 . 2))
3

有一个原始函数可以构造列表:

user=> (list 1 2 3)
(1 2 3)

结果应该是一个列表:

user=> (list? (list 1 2 3))
true

有一个函数叫做 cons:

user=> (cons 0 (list 1 2 3))
(0 1 2 3)

不知怎的,这不是一个列表:

user=> (list? (cons 0 (list 1 2 3)))
false

基本上,Clojure 确实使用不同的数据结构(-> sequences逻辑列表),具有自己的命名和语义。即使名称与 Lisp 名称相似,也不要指望它们会做同样的事情。

方案

编程语言Scheme也提供了与上面类似的cons cells。它缺少一些功能,但可以很容易地实现。例如 sublis 在 Scheme 中可能会这样实现(参见 initdr.scm):

(define (sublis alist tree)
  (if (pair? tree)
      (cons (sublis alist (car tree))
            (sublis alist (cdr tree)))
      (if (assv tree alist)
          (cdr (assv tree alist))
          tree)))
  • Clojure 确实有一个 cons 结构:clojure.lang.Cons.
  • 它用于cons次调用的结果
  • ... 仅此而已:既不是列表,也不是向量,也不是任何类型的惰性序列。
  • 也不能用于一般的objects对:尾巴/rest/cdr 是一个序列,而不是 Object.
  • 如果您 cons 将某些内容添加到列表、向量或惰性序列中, 你得到一个 Cons.
  • 但是,正如其他答案所表明的那样,没有任何功能 成交 Conses。它们通常都按顺序处理。

另一个用途:conj进入一个不确定的序列(既不是向量列表也不是集合也不是映射......)产生Cons