Common Lisp中的()与()有何区别?

5
在Common Lisp中,似乎()是一种自我评估的形式。也就是说,它会评估为它本身(或其别名nil)。因此,似乎没有必要引用它。但是,在我的quicklisp目录上使用grep可以找到许多不同人在许多不同项目中编写的'()实例。是否有技术原因需要编写引用版本?Common Lisp the Language, 2nd EditionSection 1.2.2提到了样式上的差异,您可能希望使用()强调空列表和nil假值,但未涵盖此问题。Steele使用的例子之一是:
(append '() '())

我认为这段内容可以被写得和下面这样一样好:

(append () ())

...所以为什么要在那里加入额外的QUOTEs呢?当然这不会有坏处。从风格上讲,一种形式是否普遍优于另一种形式?某些人肯定会认为引用形式使得在你改变主意并真正想要非空文字列表时更容易添加元素。或者使用引用具有某种对称性,因为非空文字列表也需要它。

这与Scheme不同,您确实需要对其进行引用。看起来在elisp中您似乎不必对其进行引用,因此可能从某种迂回的方式来看,它可能与lisp-1 vs. lisp-2有关?


实际上,CLtL2 的那个部分 扩展到了 规范的这个部分 - acelent
4个回答

9

这段引用具有非常信息丰富的特性,清楚地表明您的意思是该标记应为字面数据。尤其是当您或同事数年后重新访问代码时。

在非评估形式中(例如参数列表或类超类和插槽),您必须使用 ()(不带引号)来声明空列表,在此情况下使用引号实际上会造成伤害。实际上,您也可以使用 nil,但在声明内容为列表时为了清晰起见,不应使用。


这是来自规范的相关摘录:

1.4.1.4.4 NIL

nil has a variety of meanings. It is a symbol in the COMMON-LISP package with the name "NIL", it is boolean (and generalized boolean) false, it is the empty list, and it is the name of the empty type (a subtype of all types).

Within Common Lisp, nil can be notated interchangeably as either NIL or (). By convention, the choice of notation offers a hint as to which of its many roles it is playing.

For Evaluation?  Notation  Typically Implied Role       
----------
Yes              nil       use as a boolean.            
Yes              'nil      use as a symbol.             
Yes              '()       use as an empty list         
No               nil       use as a symbol or boolean.  
No               ()        use as an empty list.        

Figure 1-1. Notations for NIL

Within this document only, nil is also sometimes notated as false to emphasize its role as a boolean.

For example:

(print ())                          ;avoided
(defun three nil 3)                 ;avoided 
'(nil nil)                          ;list of two symbols
'(() ())                            ;list of empty lists
(defun three () 3)                  ;Emphasize empty parameter list.
(append '() '()) =>  ()              ;Emphasize use of empty lists
(not nil) =>  true                   ;Emphasize use as Boolean false
(get 'nil 'color)                   ;Emphasize use as a symbol

A function is sometimes said to “be false” or “be true” in some circumstance. Since no function object can be the same as nil and all function objects represent true when viewed as booleans, it would be meaningless to say that the function was literally false and uninteresting to say that it was literally true. Instead, these phrases are just traditional alternative ways of saying that the function “returns false” or “returns true,” respectively.


1
我想补充一下“愚蠢程序员论点”。如果我看到一个 '(),我知道我可以添加元素到它里面,因为它是一个表示0个元素的引用列表。如果我看到一个 (),在决定是否将其更改为列表之前,我可能需要考虑更多的上下文。例如:'() ⇒ '(a b c) 对我来说比 () ⇒ '(a b c) 更直观。 - BRPocock

4

即使()是自我评估的,写'()仍有其优点。这样做可以使代码更易于阅读,因为它在视觉上明显表明您正在处理数据而不是已评估的格式。

这类似于写'#(foo bar baz (+ 1 2))而不是#(foo bar baz (+ 1 2))的原因。它们都会被评估为同一事物,但前者在视觉上明显表明其中的foobarbaz(+ 1 2)没有被评估。


使用带引号的数据结构(无论是列表还是向量)使它们成为代码的一部分,修改它们是不符合规范的行为(但大多数实现都允许您这样做),这将导致未指定的结果(即实现可以自由地抛出段错误)。 - Daniel Kochmański
@DanielKochmański 这里并不是真正的问题:引用并不会改变你已经有的使用 #(a b c) 语法的字面向量,这个向量不应该被修改。 - coredump
1
我并不认同可读性的论点。添加引号看起来像是你想要与未加引号版本有所不同的行为,尽管它们评估出相同的值;我会觉得这很令人困惑(显然 OP 也很困惑)。引用、反引号等等可能很难正确理解其工作原理,而且我认为无用的引号只会增加噪音。 - coredump
一个有效的参数。在《Lisp之地》(http://landoflisp.com/)中,康拉德在关于一个`nil`值的4种表示方式的一节中提到,虽然不引用`()`是允许的,但它打破了数据而不是代码的约定。 - Sylwester

3
我从不引用NIL()T,当然也不会引用数字或字符串。
如果我在别人的代码中读到'(),我很可能会对作者的意图感到困惑,也许是他/她对Common Lisp中的引用有所误解(初学者经常表现出对引号的困惑)。
我不能说CL的某些实现在某个时刻需要引用()的可能性,但更有可能的是作者有其他Lisp风格的经验,并试图将此经验应用于Common Lisp,出于习惯或有意而为之。
唯一我认为引用()有意义的情况是当一个先前非空的常量列表被清空,而某人没有费心去删除引号时。
(defvar *options* '(debug))

...变成

(defvar *options* '())

如果您计划稍后更改默认值,保留该引用甚至可能是有意义的。


1
你的回答主要基于个人意见,我的意思是,你只是从自己的角度看待问题。直接写 '() 而没有在括号中间写任何内容来明确它是数据,这实际上非常有道理,而且比 () 更不容易混淆,() 应该用于未评估的列表形式。在我看来(嘿,毕竟这是一条评论),如果我在评估的形式中看到 (),我的想法是“这是有意为之还是作者忘记写表达式的内容了?”如果我知道作者的风格,我几乎不会眯起眼睛。 - acelent
1
@acelent 作为一种风格问题,我不认为没有理由不符合其他人的期望,显然许多人喜欢在不严格要求的情况下引用空列表。现在,如果您将 () 视为代码,您可以说 () 是读取复合形式的退化情况,其中返回值是空列表,就像 (list) 一样,有时也会使用更明确。但最终它并不改变结果,它只是伪装成了好老的 NIL。 - coredump

2
在Common Lisp中,()nil都表示nil且它们本身就是自我评估的。所有被引用的内容都会被求值为其参数,但由于参数是nil,而nil又是自我评估的,因此结果是相同的。编译后的代码很可能是相同的。
Scheme最初是在CL的前身下解释执行的,在最初的报告中,他们基本上只是制作了一个具有词法闭包和一个命名空间的LISP,没有更多的东西。它看起来更像CL而不是Scheme,但它是一个LISP1,因此空列表规则不能仅仅是关于Lisp1与Lisp2,因为Scheme从第一份报告开始就是一个Lisp1,它不需要引用,并将nil作为false值。
对于每个连续版本的报告,Scheme都在完善其语法和命名转换。修订进行了微小的更改,他们并不怕进行破坏性的更改。他们引入了新的布尔值,更改了它们的名称,将空列表的布尔值从false更改为true,删除了nilt作为#t#f的别名,并认为()是一个无效的表达式,因此需要使用'()
由于在CL中如何做并不重要,因此一些同时编写两种语言的人可能只想在他们的生活中保持一些对称性。我肯定会这样做,但我不能代表所有Scheme程序员。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接