【问题标题】:How does "Cons" work in Lisp?“缺点”在 Lisp 中是如何工作的?
【发布时间】:2015-06-24 13:25:39
【问题描述】:

我正在学习 Lisp,但我在 Lisp 编程方面没有经验。在我的部分学习中,我遇到了以下示例:

> (cons ‘a ‘(a b))  ----> (A A B)
> (cons ‘(a b) ‘a)  ----> ((A B).A)

我想知道为什么当我们有 (cons 'a '(ab)) 时响应是 (AAB) 以及为什么当我们稍微改变它并把'a(ab) 之后,响应是一个类似 ((AB).A) 的虚线列表?第一个代码行和第二个代码行有什么区别?这些代码背后发生了什么?

【问题讨论】:

标签: lisp common-lisp cons


【解决方案1】:

如果您将它们视为cons-cells,这很容易理解。

简而言之,一个 cons 单元由 恰好两个值组成。对此的正常表示法是使用点,例如:

(cons 'a 'b) ==> (A . B)

但由于列表在 LISP 中经常使用,更好的表示法是去掉点。 列表是通过让第二个元素成为一个新的 cons 单元格来创建的,最后一个结尾是一个终止符(通常是 nil,或 Common Lisp 中的 '())。所以这两个是相等的:

(cons 'a (cons 'b '())) ==> (A B)
(list 'a 'b) ==> (A B)

所以(cons 'a 'b) 创建了一个单元格[a,b],而(list 'a 'b) 将创建[a, [b, nil]]。注意 cons 单元格中编码列表的约定:它们以内部 nil 结尾。

现在,如果您将'a 放入最后一个列表,您将创建一个包含[[a, [b, nil]], a] 的新缺点单元格。由于这不是一个“正确”的列表,即它不是以nil 结尾的,所以写出来的方法是使用点:(cons '(a b) 'a) ==> ((a b) . a)

如果没有打印点,它必须是一个结构为[[a, [b, nil]], [a, nil]] 的列表。

你的例子

当您执行(cons 'a '(a b)) 时,它将采用符号'a 和列表'(a b) 并将它们放入一个新的cons 单元格中。所以这将包括[a, [a, [b, nil]]]。由于这自然以内部nil 结尾,因此它是不带点的。

至于(cons '(a b) 'a),现在你会得到[[a, [b, nil]], a]。这确实以内部nil 终止,因此将使用点符号。

我们可以使用 cons 使最后一个示例以内部 nil 结尾吗?是的,如果我们这样做了

(cons '(a b) (cons 'a '())) ==> ((A B) A)

最后,

(list '(a b) 'a))

等价于

(cons (cons (cons 'a (cons 'b '())) (cons 'a '())))

【讨论】:

  • 谢谢,但 (cons ‘a ‘(a b)) 和 (cons ‘(a b) ‘a) 有什么区别,为什么这两者的行为不同?
  • @AmirJalilifard 他们的行为并没有什么不同。 (cons 'a '(a b)) 返回一个 cons 单元,其 car 为 'a,cdr 为 '(a b)。另一方面 (cons '(a b) 'a) 返回一个 cons 单元格,其 car 为 '(a b) 且 cdr 为 'a。缺点细胞只是成对的。代替 cons、car 和 cdr,您可以说 make-pair、left 和 right 并获得相同的行为。 makePair(2,3) 与 makePair(3,2) 明显不同,因为在第一个中,2 和 3 是左右的,而在第二个中,2 和 3 是左右的。
【解决方案2】:

查看此可视化:

CL-USER 7 > (sdraw:sdraw '(A A B))

[*|*]--->[*|*]--->[*|*]--->NIL
 |        |        |
 v        v        v
 A        A        B

CL-USER 8 > (sdraw:sdraw '((A B) . A))

[*|*]--->A
 |
 v
[*|*]--->[*|*]--->NIL
 |        |
 v        v
 A        B

还有:

CL-USER 9 > (sdraw:sdraw '(A B))

[*|*]--->[*|*]--->NIL
 |        |
 v        v
 A        B

CL-USER 10 > (sdraw:sdraw (cons 'A '(A B)))

[*|*]--->[*|*]--->[*|*]--->NIL
 |        |        |
 v        v        v
 A        A        B

CL-USER 11 > (sdraw:sdraw (cons '(A B) 'A))

[*|*]--->A
 |
 v
[*|*]--->[*|*]--->NIL
 |        |
 v        v
 A        B

【讨论】:

    【解决方案3】:

    cons 是一个可以包含两个值的数据结构。例如(cons 1 2) ; ==> (1 . 2)。第一部分是car,第二部分是cdr。如果cdrnillist,则conslist。因此 (1 . (2 . (3 . ()))) 是一个列表。

    当打印cons 时,如果cdrconsnil,则省略点。 cdr 的外圆括号也被省略。因此(3 . ()) 被打印(3)(1 . (2 . (3 . ()))) 被打印(1 2 3)。它是相同的结构,但具有不同的可视化。 car 中的cons 没有这个规则。

    cdr 是一个列表时,读取函数用点和奇怪的异常打印格式读取cons。它在读取时的行为就像cons

    对于readprint 都有一个特殊规则,即使是cons 的链,列表的错觉也是完整的。

    (cons ‘a ‘(a b))  ----> (A . (A B)) 
    (cons ‘(a b) ‘a)  ----> ((A B) . A)
    

    打印时,第一个是 3 个元素的列表,因为 cdr 是一个列表。

    【讨论】:

      【解决方案4】:

      一个列表(a b c)被表示(在内部存储)为三个 cons-cells:(cons 'a (cons 'b (cons 'c '())。请注意,最后一对的 cdr 中有 '()。

      最后一个 cdr 为 '() 的一系列 cons-cell 被打印机打印为一个列表。该示例因此打印为 (a b c)。

      让我们看看:(cons 'a '(a b))

      列表 '(a b) 表示为 (cons 'a (cons 'b '())。这意味着 (cons 'a '(a b)) 产生:(cons 'a (cons 'a (cons 'b '()))

      我们来看一下:(cons '(a b) 'a)

      列表 '(a b) 表示为 (cons 'a (cons 'b '())。这意味着 (cons (cons '(a b) 'a)) 产生 (cons (cons 'a (cons 'b '()) 'a)

      请注意,本系列不以'() 结尾。显示打印机使用点表示法。 ( ... . 'a) 表示一个值由一系列 cons-cells 组成,并且最后一个 cdr 包含 'a。值(cons (cons 'a (cons 'b '()) 'a) 因此打印为'((a b) . a)

      【讨论】:

        【解决方案5】:

        cons 只是一个对数据类型。例如,(cons 1 2) 是一对12,打印出来的两个元素之间用点隔开,比如(1 . 2)

        列表在内部表示为嵌套的 conses,例如列表(1 2 3)(cons 1 (cons 2 (cons 3 '()))

        【讨论】:

          猜你喜欢
          • 2013-12-07
          • 1970-01-01
          • 2012-02-01
          • 2014-10-30
          • 1970-01-01
          • 2012-12-11
          • 1970-01-01
          • 2017-06-25
          • 2012-09-29
          相关资源
          最近更新 更多