Common lisp 为什么来自ANSI Common Lisp的这个范围界定示例不能按预期工作?

Common lisp 为什么来自ANSI Common Lisp的这个范围界定示例不能按预期工作?,common-lisp,scoping,Common Lisp,Scoping,我是lisp新手,正在学习Paul Graham编写的ANSI Common lisp,其中一个练习是定义一个类似apply的函数,其中在返回之前打印出来的任何数字都将默认以八进制打印 我尝试了以下方法: (let ((*print-base* 8)) (defun like-apply (&rest args) (apply #'apply args))) 但它并没有像预期的那样起作用: (like-apply #'princ '(8)); returns 8 8 (ex

我是lisp新手,正在学习Paul Graham编写的ANSI Common lisp,其中一个练习是定义一个类似apply的函数,其中在返回之前打印出来的任何数字都将默认以八进制打印

我尝试了以下方法:

(let ((*print-base* 8))
  (defun like-apply (&rest args)
    (apply #'apply args)))
但它并没有像预期的那样起作用:

(like-apply #'princ '(8)); returns 8 8 (expecting 10 8)
但是,以下方法可行:

(defun apply8 (&rest args)
  (let ((*print-base* 8))
    (apply #'apply args)))
正确返回:

(apply8 #'princ '(8)); returns 10 8 (as expected)

所以我的问题是为什么第二个例子有效,而第一个却不行?两者似乎都在操纵
*print base*
变量。

您观察到的行为是正确的

比较一下会很有启发性

(let ((*print-base* 8))
  (defun f1 ()
    *print-base*))

发现
(f1)
返回
10
,而
(f2)
返回
8


这是因为
*print base*
是围绕
f1
的定义绑定的,所以在定义
f1
时,它是
8
,而在执行时则不是。

Common Lisp使用let绑定词法变量和“特殊”(动态)变量。您期望的行为是词汇性的,而您观察到的行为是动态的。打印机变量都是特殊的,因此让我们为它们创建一个动态绑定

打印机变量有时用于说明为什么动态绑定很有用。例如,可以通过动态绑定启用绑定*print base*来控制princ的行为,否则princ将在定义princ时引用激活的*print base*绑定


这种行为是许多普通Lisp程序员坚持特殊变量的“耳罩”命名约定的主要原因。请注意,defvar和defparameter都创建了特殊变量。

我想我明白了,因为它是一个特殊变量(根据上面m-n的回答),所以它具有动态范围。这意味着在第一个示例中,它返回到print base具有的某个全局默认值(如果没有更改,则为10)。在第二个示例中,每次调用f2时,它都会更改print base的值。感谢你们两位。感谢你们如此清楚地解释了这一点,以及关于defparameter和defvar都创建特殊变量的附加提示。当他们现在很特别的时候,这完全有道理。
(defun f2 ()
  (let ((*print-base* 8))
    *print-base*))