这是另一个答案的附录。另一个答案解释了 lisp-1s(具有用于函数和变量绑定的单个命名空间的 lisp)和 lisp-2s(具有用于函数绑定的单独命名空间的 lisp)之间的区别。
我想解释为什么 lisp-2 可能会使事情变得更好,尤其是为什么它在历史上会这样做。
首先让我们考虑一下Scheme代码:
(define (foo x)
(let ([car (car x)])
... in here (car ...) is probably not going to get the car
(bar car)))
(define (bar thing)
... but in here, car is what you expect ...)
所以,在foo
我有绑定car
到车的说法。这在 Scheme 中可能是一种糟糕的风格,这意味着,在该绑定的主体中,car
当用作函数时,可能不会像您期望的那样做。但是这个问题只在 的 绑定的词法范围内很重要car
:例如,它并不重要bar
。
现在,在 Common Lisp 中,我可以编写等效代码:
(defun foo (x)
(let ((car (car x)))
... (car ...) is fine in here ...
(bar car)))
(defun bar (thing)
... and here ...)
所以这可能会好一点:在绑定的主体内,car
它仍然可以car
用作函数,并且实际上编译器可以做出非常强的假设,即car
语言定义的函数并且 CL 在标准中有措辞这确保了这始终是正确的。
这意味着,在风格上,在 CL 中,这样的事情可能是可以的。特别是我经常做这样的事情:
(defmethod manipulate-thing ((thing cons))
(destructuring-bind (car . cdr) thing
...use car & cdr...))
我认为这很好:在 Scheme 中,等价物会很可怕。
这就是 lisp-2 非常方便的原因之一。然而,有一个更强大的,它不适用于 CL,但适用于 elisp。
考虑一下,在 elisp 中,这段代码:
(defun foo (x)
(let ((car (car x))
(cdr (cdr x)))
(bar car cdr)))
(defun bar (thing-1 thing-2)
...)
现在有一个关于 elisp 的重要知识:默认情况下它是动态作用域的。这意味着,当bar
从 调用时,和foo
的绑定在中可见car
car
bar
。
因此,例如,如果我重新定义bar
为:
(defun bar (thing-1 thing-2)
(cons cdr thing-1))
然后:
ELISP> (foo '(1 . 2))
(2 . 1)
所以,现在,想想如果 elisp 是一个 lisp-1 会发生什么:任何调用 from 的函数foo
都会发现它(car x)
并没有达到预期的效果!这是一场灾难:这意味着如果我将一个函数的名称——任何函数,包括我可能不知道存在的函数——绑定为一个变量,那么该绑定的动态范围内的任何代码都不会做它应该做的事情。
因此,对于具有动态范围的 Lisp,正如 elisp 历史上拥有并且默认情况下仍然拥有的那样,作为 lisp-1 是一场灾难。好吧,从历史上看,很多 lisp 实现确实具有动态范围(至少在解释代码中:编译代码具有不同的范围规则是很常见的,并且范围规则通常通常有些不连贯)。所以对于那些实现来说,作为一个 lisp-2 是一个非常重要的优势。而且,当然,一旦存在大量假定 lisp-2-ness 的代码,即使在词法范围语言中具有优势,但以兼容性为目标的语言(例如 CL)更容易保持 lisp-2s不太清楚。
注意:很久以前我使用过一个 lisp,它既是动态范围的(至少在解释器中?)和一个 lisp-1。因此,我至少有一次非常糟糕的经历(我认为需要硬重置一台已经变得紧张的多用户机器,因为它分页太多,这让我不受所有其他用户的欢迎)。