为什么 (let ((x 'huh?)) (cons (boundp 'x) x)) 的计算结果为 (NIL . HUH?)?

How come (let ((x 'huh?)) (cons (boundp 'x) x)) evaluates to (NIL . HUH?)?

我不明白这个:

CL-USER> (let ((x 'huh?)) (cons (boundp 'x) x))
(NIL . HUH?)

我曾预计在上面的 let 表达式中,x 会被绑定,因此整个表达式的计算结果会是 (t . huh?)。否则,如果(与我的预期相反)x 没有绑定在 let 的主体中,那么至少上面表达式的评估会导致错误(由于我的已将未绑定变量作为第二个参数传递给 cons).

让我更加困惑的是,Common Lisp HyperSpec 对 boundp says 的描述:

Returns true if symbol is bound; otherwise, returns false.

...其中“绑定”一词超链接到此 glossary definition(我强调)1:

bound adj., v.t. 1. adj. having an associated denotation in a binding. ``The variables named by a let are bound within its body.'' See unbound. 2. adj. having a local binding which shadows[2] another. ``The variable print-escape is bound while in the princ function.'' 3. v.t. the past tense of bind.

此外,let 的 CLHS 文档说了以下内容(我的重点):

...all of the variables varj are bound to the corresponding values; ...

当然,boundp 的 HyperSpec 页面(我之前已经链接到它)有以下示例:

(let ((x 2)) (boundp 'x)) =>  false

...这确实可以证明我所观察到的实际上是“官方记录的行为”这一断言,但鉴于我在上面引用的所有其他内容,这种狭隘正确的理由并不能让人感到安慰。

有人可以为我解决这个巨大的(并且希望只是表面上的)矛盾吗?


1 我意识到上面突出显示的短语只是“绑定”一词在句子中的用法示例,但它会一个真正反常的例子,如果它所说的与 Common Lisp 的实际情况完全相反。

boundp用于判断符号是否绑定在全局环境中。请注意以下来自 HyperSpec 的两个示例:

(let ((x 2)) (boundp 'x)) ;=>  false  
(let ((x 2)) (declare (special x)) (boundp 'x)) ;=>  true

页面底部的注释说:

The function bound determines only whether a symbol has a value in the global environment; any lexical bindings are ignored.

注释中 bound 而不是 boundp 的出现似乎是错字。无论如何,CLTL2 对此更具体一些:

boundp is true if the dynamic (special) variable named by symbol has a value; otherwise, it returns nil.

注意 fboundp 也有类似的限制;这是来自 HyperSpec:

的示例
(flet ((my-function (x) x))  
  (fboundp 'my-function)) ;=>  false

boundp 处理词法变量没有多大意义。来自 HyperSpec 3.1.2.1.1.1 Lexical Variables:

A lexical variable always has a value. There is no operator that introduces a binding for a lexical variable without giving it an initial value, nor is there any operator that can make a lexical variable be unbound.

这就是说词法变量总是绑定在它们的词法环境中。但是动态变量 可能 bound or unbound 之一,两者中的哪一个取决于提问的环境:

The value part of the binding for a dynamic variable might be empty; in this case, the dynamic variable is said to have no value, or to be unbound. A dynamic variable can be made unbound by using makunbound....

A dynamic variable is unbound unless and until explicitly assigned a value, except for those variables whose initial value is defined in this specification or by an implementation.

这可能有点令人困惑,但是 BOUNDP 的规范确实是这样说的:

The function bound [sic (it should be boundp)] determines only whether a symbol has a value in the global environment; any lexical bindings are ignored.

因此它只会通知您给定符号是否已绑定到全局环境中,如果变量的值单元格设置为某个值(请参阅 SYMBOL-VALUE),就会发生这种情况, 或者变量被声明为 special 并且之前被 let 形式绑定。第二种情况尤其发生在用 defvardefparameter 声明的变量上,但也发生在你声明为特殊的任何变量上:

(let ((%my-var% 0))
  (declare (special %my-var%))
  ...)

请注意,每次您想使用 %my-var% 时,您都需要使用该声明,除非您在全局声明它。

(defun use-my-var (input)
  (declare (special %my-var%))
  (print `(:my-var ,%my-var% :input ,input)))

当您编写 use-my-var 函数时,您通常可以毫无问题地识别 input 已绑定,事实上,如果不是这种情况,编译器会警告您。对于词法作用域,(boundp x) 将编译为常量值 T 或 NIL。检查符号的 symbol-value 是全局绑定还是动态绑定更有趣。

这里,由于%my-var%是一个特殊的变量,所以在不同的调用上下文中可以绑定也可以不绑定:

(let ((%my-var% 0))
  (declare (special %my-var%))
  (use-my-var 1))
=> (:my-var 0 :input 1)

(use-my-var 0)
;; ERROR: The variable %MY-VAR% is unbound.