【问题标题】:Is an empty list in Lisp built from a cons cell?Lisp 中的空列表是从 cons 单元格构建的吗?
【发布时间】:2013-05-12 10:51:25
【问题描述】:

我正在尝试在 JavaScript 中模拟类似 Lisp 的列表(只是一个没有实际原因的练习),但我正在努力弄清楚如何最好地表示一个空列表。

空列表只是一个 nil 值还是存储在 cons 单元格中?

我可以:

(car '())
NIL
(cdr '())
NIL

但一个空列表肯定不能是(cons nil nil),因为它与存储单个nil 的列表无法区分。它需要存储一些其他特殊值。

另一方面,如果空列表不是从 cons 单元构建的,那么似乎不可能有一个一致的高级接口来将单个值附加到现有列表。像这样的函数:

(defun append-value (list value) ...

会修改它的参数,但前提是它不是一个空列表,这看起来很难看。

【问题讨论】:

  • 我将您的帖子编辑为使用 defun 而不是来自 Scheme 的 define。我没想到你说的是Scheme,因为Scheme没有nil,它不允许你传入一个空列表给carcdr,以及使用直接列表在 Scheme 社区中,突变更加不受欢迎。
  • 还有head sentinel technique,供您个人使用:从非空单例列表开始,例如(1) 或其他;以统一的方式对其进行手术处理并最终返回其cdr。允许以一个额外的 cons 单元分配为代价来简化代码。

标签: lisp null cons


【解决方案1】:

空列表就是nil 符号(根据定义,符号不是conses)。如果给定nilcarcdr 被定义为返回 nil

对于列表变异函数,它们返回一个您应该重新分配给变量的值。例如,查看nreverse 函数的规范:它可能会修改给定的列表,也可能不会,并且您应该使用返回值,而不是依赖它来就地修改。

即使nconc,典型的破坏性附加函数,也是这样工作的:它的返回值是你应该使用的附加列表。 指定就地修改给定列表(最后一个列表除外),但如果你给它nil 作为第一个参数,它不能很好地修改它,所以你仍然必须使用返回值。

【讨论】:

    【解决方案2】:

    信不信由你,这实际上是一个宗教问题。

    人们敢于将某些方言称为某种 Lisp,其中空列表是某种组合或聚合对象,而不仅仅是像 nil 这样的原子。

    例如,在“MatzLisp”(更广为人知的 Ruby)中,列表实际上是数组。

    在 NewLisp 中,列表是容器:列表类型的对象,其中包含项目的链表,因此空列表是空容器。 [Reference].

    在 Lisp 语言中,不是这种壮观的集群摸索,空列表是原子,非空列表是二进制单元格,其中一个字段保存第一项,另一个字段保存列表的其余部分.列表可以共享后缀。给定像(1 2 3) 这样的列表,我们可以使用cons 创建(a 1 2 3)(b c 1 2 3),它们都共享(1 2 3) 的存储空间。

    (在 ANSI Common Lisp 中,空列表原子 () 与符号 nil 是同一个对象,它的计算结果为自身,也用作布尔值 false。在 Scheme 中,() 不是符号,并且与布尔型 false #f 对象不同。但是,Scheme 列表仍然由对组成,并由原子终止。)

    评估(car nil) 的能力不会自动遵循列表的 cons-and-nil 表示,如果我们查看古老的 Lisp 文档,例如 1960 年初的 Lisp 1.5 手册,我们会发现这是缺席的。最初,car 严格来说是一种访问 cons 单元格字段的方法,并且严格要求使用 cons 单元格参数。

    允许(car nil) 工作(这样黑客可以从他们的程序中删除许多无用的代码行)这样的好主意并不是一夜之间出现的。允许(car nil) 的想法可能是从InterLisp 中出现的。无论如何,Evolution Of Lisp 论文声称 MacLisp(Common Lisp 的重要前身之一,与 20 年后出现的 Apple Macintosh 无关)模仿了 InterLisp(另一个重要的前辈)。

    像这样的小细节决定了愉快的编程和对着显示器说脏话之间的区别:例如,参见A Short Ballad Dedicated to the Growth of Programs,灵感来自一个 Lisp 程序员与一种无礼方言的斗争,在这种方言中,car 无法访问空列表,并且不要用作布尔值 false。

    【讨论】:

    • 我不认为人们真的试图将 Ruby 当作 Lisp 来冒充。真的?!此外,在最后一段中对 Scheme 进行了有趣的抨击。 ;-)
    【解决方案3】:

    NIL 在 Common Lisp 中有点奇怪,因为

    • 这是一个符号(意味着symbolp 返回T
    • 是一个列表
    • 不是 cons 单元格(consp 返回 NIL
    • 无论如何你都可以拿走CARCDR

    请注意,这背后的原因可能也是历史原因,您不应认为这是唯一合理的解决方案。其他 Lisp 方言做出了不同的选择。

    【讨论】:

    • 因此,Common Lisp 列表也有点奇怪,因为“列表是可变的吗?”的答案是“有时”。
    • @JanWrobel 列表是一种错觉。真实数据在 conses 和 nil 中。缺点是可变的。 nil 不是。就是这样。
    • @ChrisJester-Young:同意这种错觉;在 CL 中讨论列表的可变性是无稽之谈,因为列表不是对象。然而,Lisp 的大部分想法并不需要依赖于这种方法,您可以构建一个 lisp 方言,其中list 是一流的对象。例如,我构建了一个针对 javascript 的 lisp 编译器,它使用 js 数组对象来表示列表(没有 CARCDR 并且只能在元素级别而不是尾部级别进行共享)。到目前为止,我对这个解决方案非常满意。
    • 别忘了:这是逻辑错误值! (nil 有很多东西是我讨厌的一个 CL 功能。需要解决方法代码。考虑到 CL 的实用性和(有时尴尬的)美感,所有其他缺点都不值得一提。)
    【解决方案4】:

    用你的 Lisp 解释器试试:

    (eq nil '())
    => t
    

    nil/空列表上进行操作时,一些操作是特殊情况下执行非正交(甚至是好奇:-)的事情。您正在调查的carcdr 的行为就是其中之一。

    nil 作为空列表的身份是您了解 Lisp 的第一件事。我试图想出一个很好的谷歌热门,但我只会选择一个,因为有很多:http://www.cs.sfu.ca/CourseCentral/310/pwfong/Lisp/1/tutorial1.html

    【讨论】:

    • 是的,我知道身份,但我想知道内部表示。所以不可能有一个关于其参数的具有一致接口的附加值函数?
    • @JanWrobel 正如我在回答中提到的,您的界面应该是返回新值,这可能是相同的列表,也可能不是相同的列表,具体取决于您是否能够就地修改它。无论如何,调用者都应该使用返回值,必要时重新分配给他们的变量。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-21
    • 2021-12-30
    • 1970-01-01
    • 2021-04-21
    • 2018-11-29
    • 1970-01-01
    相关资源
    最近更新 更多