这是另一个答案的附录。另一个答案解释了 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的绑定在中可见carcarbar。
因此,例如,如果我重新定义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。因此,我至少有一次非常糟糕的经历(我认为需要硬重置一台已经变得紧张的多用户机器,因为它分页太多,这让我不受所有其他用户的欢迎)。