我试图在JavaScript中模拟类似lisp的列表(只是一个没有实际原因的练习),但我正在努力找出如何最好地表示空列表。
是一个空列表只是一个nil
值或它是在引擎盖下存储在一个cons单元格?
我可以:
(car '())
NIL
(cdr '())
NIL
,但空列表肯定不可能是(cons nil nil)
,因为它与存储单个nil
的列表无法区分。它需要存储一些其他的特殊值。
另一方面,如果空列表不是从cons单元格构建的,则似乎不可能有一个一致的高级接口来向现有列表添加单个值。如下函数:
(defun append-value (list value) ...
将修改它的实参,但前提是它不是空列表,这看起来很难看。
有一些方言,人们敢于称之为某种Lisp,在这些方言中,空表是某种集合对象,而不仅仅是像nil
那样的原子。
例如,在"MatzLisp"(即Ruby)中,列表实际上是数组。
在NewLisp中,列表是容器:列表类型的对象包含项目的链表,因此空列表是空容器。[引用].
在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和nil表示中得到,如果我们看一下古老的Lisp文档,比如1960年代早期的Lisp 1.5手册,我们会发现这是不存在的。最初,car
严格地是访问cons单元格字段的一种方式,并且严格地需要一个cons单元格参数。
(car nil)
只工作这样的好主意(这样黑客就可以从他们的程序中删减许多无用的代码行)不是一夜之间出现的。允许(car nil)
的想法可能来自InterLisp。无论如何, Lisp的演变论文声称MacLisp (Common Lisp的重要前身之一,与20年后出现的Apple Macintosh无关)模仿了InterLisp(另一个重要的前身)的这一特性。
像这样的小细节可以区分愉快的编程和对监视器的咒骂:例如,参考一个Lisp程序员与一种令人讨厌的方言的斗争所启发的《献给程序成长的短谣》,在这种方言中,空列表不能用car
访问,也不能作为布尔值false。
空列表就是nil
符号(根据定义,符号不是conses)。car
和cdr
被定义为如果给定nil
则返回nil
。
对于列表突变函数,它们返回一个值,您应该将该值重新赋值给变量。例如,看看nreverse
函数的规范:它可以修改给定的列表,也可以不修改,您应该使用返回值,而不是依赖它来进行就地修改。
即使是典型的析构式追加函数nconc
也是这样工作的:它的返回值是你应该使用的追加列表。它被指定为就地修改给定列表(最后一个列表除外),但是如果您将nil
作为第一个参数给它,它就不能很好地修改它,因此您仍然必须使用返回值。
NIL
在Common Lisp中有点奇怪,因为
- 它是一个符号(意思是
symbolp
返回T
) - 是一个列表
- 不是cons单元格(
consp
返回NIL
) - 你可以拿走
CAR
和CDR
的
注意,这背后的原因可能也是历史的,你不应该认为这是唯一合理的解决方案。其他Lisp方言有不同的选择。
用你的Lisp解释器试试:
(eq nil '())
=> t
当对nil
/空列表进行操作时,有几个操作是特殊的,以执行不正交(甚至奇怪的:-)操作。你正在调查的car
和cdr
的行为就是其中之一。
nil
作为空列表的身份是您学习Lisp的第一件事。我试着找一个好的Google搜索结果但我只选一个,因为有太多了http://www.cs.sfu.ca/CourseCentral/310/pwfong/Lisp/1/tutorial1.html