动态绑定和宏
Dynamic Binding and Macros
考虑以下代码:
CL-USER> (defmacro sum (a b)
(+ a b))
SUM
CL-USER> (let ((alpha 3) (beta -1))
(sum alpha beta))
; in: LET ((ALPHA 3) (BETA -1))
; (SUM ALPHA BETA)
;
; caught ERROR:
; during macroexpansion of (SUM ALPHA BETA). Use *BREAK-ON-SIGNALS* to intercept.
;
; Argument X is not a NUMBER: ALPHA
; (LET ((ALPHA 3) (BETA -1))
; (SUM ALPHA BETA))
;
; caught STYLE-WARNING:
; The variable ALPHA is defined but never used.
;
; caught STYLE-WARNING:
; The variable BETA is defined but never used.
;
; compilation unit finished
; caught 1 ERROR condition
; caught 2 STYLE-WARNING conditions
; Evaluation aborted on #<SB-INT:COMPILED-PROGRAM-ERROR {10030E4223}>.
基本上有两个原因(我能想到)导致此代码失败:
1. 宏 sum
首先根据两个变量 alpha
和 beta
求值,这两个变量作为符号发送到宏中。因此,宏中要计算的代码是:
(+ 'alpha 'beta)
这行不通,因为我们不能添加两个符号。
2. 变量alpha
和beta
是词法绑定,因此,宏的代码无法访问[=17=的符号值] 和 beta
.
因此,重新定义sum
:
CL-USER> (defmacro sum (a b)
(+ (symbol-value a) (symbol-value b)))
WARNING: redefining COMMON-LISP-USER::SUM in DEFMACRO
SUM
这里,宏sum
将评估提供给它的符号的值。如果它在符号的范围内,它只能这样做。因此,为了做到这一点,我们可以使 alpha
和 beta
动态绑定。
此外,为了检查动态绑定是否有效,我们可以创建一个函数 dynamic-checker
,定义如下:
CL-USER> (defun dynamic-checker ()
(+ alpha beta))
; in: DEFUN DYNAMIC-CHECKER
; (+ ALPHA BETA)
;
; caught WARNING:
; undefined variable: ALPHA
;
; caught WARNING:
; undefined variable: BETA
;
; compilation unit finished
; Undefined variables:
; ALPHA BETA
; caught 2 WARNING conditions
DYNAMIC-CHECKER
最后,我们可以在 REPL 中评估这段代码:
CL-USER> (let ((alpha 3) (beta -1))
(declare (special alpha))
(declare (special beta))
(print (dynamic-checker))
(sum alpha beta))
这给了我们错误:
; in: LET ((ALPHA 3) (BETA -1))
; (SUM ALPHA BETA)
;
; caught ERROR:
; during macroexpansion of (SUM ALPHA BETA). Use *BREAK-ON-SIGNALS* to intercept.
;
; The variable ALPHA is unbound.
;
; compilation unit finished
; caught 1 ERROR condition
2 ; Evaluation aborted on #<SB-INT:COMPILED-PROGRAM-ERROR {1003F23AD3}>.
CL-USER>
注意错误代码末尾的2
。这是由函数 dynamic-checker
返回的,它添加了 alpha
和 beta
,即使它们不是它的参数,这证明了变量 alpha
和 beta
可以被 let
.
的成员动态访问
因此,宏 sum
现在应该可以工作了,因为之前发生的两个问题都已解决。
但这显然不是这种情况,我遗漏了一些东西。
任何帮助表示赞赏。
解释器和编译器与交互性
Common Lisp 允许解释器和编译器。您可以交互式地使用 read-eval-print-loop 并不意味着该实现使用了解释器。它可以增量编译代码,然后调用编译后的代码。一个 Lisp 解释器 运行 是 Lisp 表示的代码。默认情况下,SBCL 不使用解释器。它使用编译器。
使用口译员
LispWorks 有解释器。让我们使用它:
CL-USER 8 > (defun test ()
(let ((alpha 3) (beta -1))
(declare (special alpha))
(declare (special beta))
(print (dynamic-checker))
(sum alpha beta)))
TEST
CL-USER 9 > (test)
2
2
因此代码有效,因为 Lisp 解释器执行表单,当它看到宏时,它会在运行中扩展它。绑定可用。
让我们使用 LispWorks 步进器,它使用解释器。 :s
是 step 命令。
(step (test))
(TEST) -> :s
(LET ((ALPHA 3) (BETA -1))
(DECLARE (SPECIAL ALPHA))
(DECLARE (SPECIAL BETA))
(PRINT (DYNAMIC-CHECKER))
(SUM ALPHA BETA)) -> :s
3 -> :s
3
-1 -> :s
-1
(PRINT (DYNAMIC-CHECKER)) -> :s
(DYNAMIC-CHECKER) -> :s
(+ ALPHA BETA) -> :s
ALPHA -> :s
3
BETA -> :s
-1
2
2
2 ; <- output
2
(SUM ALPHA BETA) <=> 2 ; <- macro expansion to 2
2 -> :s
2 ; 2 evaluates to itself
2
2
2
编译失败
但是我们无法编译您的代码:
CL-USER 10 > (compile 'test)
Error: The variable ALPHA is unbound.
1 (continue) Try evaluating ALPHA again.
2 Return the value of :ALPHA instead.
3 Specify a value to use this time instead of evaluating ALPHA.
4 Specify a value to set ALPHA to.
5 (abort) Return to level 0.
6 Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
编译器会尝试展开宏,但不会 运行 代码。由于未执行 LET
形式(编译器仅将其编译为其他内容,但不执行它),因此 alpha
.
没有绑定
风格
一般来说,避免在封闭代码中编写需要 运行时间状态的宏是个好主意。这样的宏只能由解释器执行。
根据用户 jkiiski 的评论,我觉得您需要了解这两个操作(宏操作和 program/function 应用程序)何时在典型的编译代码中发生。此外,我不建议您编写您定义的宏,但出于教育目的会使用它们。
让我们首先明确声明两个特殊符号,并将它们绑定到一些值:
CL-USER> (defvar alpha 6)
6
CL-USER> (defvar beta 5)
5
这不仅建立了两个变量special/dynamic,而且还分别给它们绑定了值6和5。
更重要的是,通过这样做,我们在您的宏中的示例或您的代码将被评估之前建立变量和绑定。
现在我们 运行 你的最终表格,我们应该得到以下内容:
CL-USER> (let ((alpha 3) (beta -1))
(declare (special alpha))
(declare (special beta))
(print (dynamic-checker))
(sum alpha beta))
2
11
由于 (dynamic-checker)
的打印,第一个值 2 被打印出来。返回值 11 是因为宏 sum
解析为值 11,然后有效地取代了形式 (sum alpha beta)
并在 (let ...)
的实际计算中返回代码。
要认识到的重要一点是,宏是在编译时求值的:也就是说,在编译 (let ...)
和 运行 的最终形式之前。因为它是在您的 (let ...)
表单之前评估的,所以当宏解析时,您的 let 表单不会建立 3 和 -1 与符号 alpha
和 beta
的绑定。在此之前,我很厚颜无耻地对值 6 和 5 进行了显式绑定,这就是宏所看到的。
宏解析成功后,你的let表单有效变为:
(let ((alpha 3) (beta -1))
(declare (special alpha))
(declare (special beta))
(print (dynamic-checker))
11)
这就解释了为什么我们现在没有错误,是什么导致了你之前的例子中的错误,以及为什么我们从我上面指定的添加中得到了输出,我们可以明确地看到 macro/let 形式是什么时候正在解决。
考虑以下代码:
CL-USER> (defmacro sum (a b)
(+ a b))
SUM
CL-USER> (let ((alpha 3) (beta -1))
(sum alpha beta))
; in: LET ((ALPHA 3) (BETA -1))
; (SUM ALPHA BETA)
;
; caught ERROR:
; during macroexpansion of (SUM ALPHA BETA). Use *BREAK-ON-SIGNALS* to intercept.
;
; Argument X is not a NUMBER: ALPHA
; (LET ((ALPHA 3) (BETA -1))
; (SUM ALPHA BETA))
;
; caught STYLE-WARNING:
; The variable ALPHA is defined but never used.
;
; caught STYLE-WARNING:
; The variable BETA is defined but never used.
;
; compilation unit finished
; caught 1 ERROR condition
; caught 2 STYLE-WARNING conditions
; Evaluation aborted on #<SB-INT:COMPILED-PROGRAM-ERROR {10030E4223}>.
基本上有两个原因(我能想到)导致此代码失败:
1. 宏 sum
首先根据两个变量 alpha
和 beta
求值,这两个变量作为符号发送到宏中。因此,宏中要计算的代码是:
(+ 'alpha 'beta)
这行不通,因为我们不能添加两个符号。
2. 变量alpha
和beta
是词法绑定,因此,宏的代码无法访问[=17=的符号值] 和 beta
.
因此,重新定义sum
:
CL-USER> (defmacro sum (a b)
(+ (symbol-value a) (symbol-value b)))
WARNING: redefining COMMON-LISP-USER::SUM in DEFMACRO
SUM
这里,宏sum
将评估提供给它的符号的值。如果它在符号的范围内,它只能这样做。因此,为了做到这一点,我们可以使 alpha
和 beta
动态绑定。
此外,为了检查动态绑定是否有效,我们可以创建一个函数 dynamic-checker
,定义如下:
CL-USER> (defun dynamic-checker ()
(+ alpha beta))
; in: DEFUN DYNAMIC-CHECKER
; (+ ALPHA BETA)
;
; caught WARNING:
; undefined variable: ALPHA
;
; caught WARNING:
; undefined variable: BETA
;
; compilation unit finished
; Undefined variables:
; ALPHA BETA
; caught 2 WARNING conditions
DYNAMIC-CHECKER
最后,我们可以在 REPL 中评估这段代码:
CL-USER> (let ((alpha 3) (beta -1))
(declare (special alpha))
(declare (special beta))
(print (dynamic-checker))
(sum alpha beta))
这给了我们错误:
; in: LET ((ALPHA 3) (BETA -1))
; (SUM ALPHA BETA)
;
; caught ERROR:
; during macroexpansion of (SUM ALPHA BETA). Use *BREAK-ON-SIGNALS* to intercept.
;
; The variable ALPHA is unbound.
;
; compilation unit finished
; caught 1 ERROR condition
2 ; Evaluation aborted on #<SB-INT:COMPILED-PROGRAM-ERROR {1003F23AD3}>.
CL-USER>
注意错误代码末尾的2
。这是由函数 dynamic-checker
返回的,它添加了 alpha
和 beta
,即使它们不是它的参数,这证明了变量 alpha
和 beta
可以被 let
.
的成员动态访问
因此,宏 sum
现在应该可以工作了,因为之前发生的两个问题都已解决。
但这显然不是这种情况,我遗漏了一些东西。
任何帮助表示赞赏。
解释器和编译器与交互性
Common Lisp 允许解释器和编译器。您可以交互式地使用 read-eval-print-loop 并不意味着该实现使用了解释器。它可以增量编译代码,然后调用编译后的代码。一个 Lisp 解释器 运行 是 Lisp 表示的代码。默认情况下,SBCL 不使用解释器。它使用编译器。
使用口译员
LispWorks 有解释器。让我们使用它:
CL-USER 8 > (defun test ()
(let ((alpha 3) (beta -1))
(declare (special alpha))
(declare (special beta))
(print (dynamic-checker))
(sum alpha beta)))
TEST
CL-USER 9 > (test)
2
2
因此代码有效,因为 Lisp 解释器执行表单,当它看到宏时,它会在运行中扩展它。绑定可用。
让我们使用 LispWorks 步进器,它使用解释器。 :s
是 step 命令。
(step (test))
(TEST) -> :s
(LET ((ALPHA 3) (BETA -1))
(DECLARE (SPECIAL ALPHA))
(DECLARE (SPECIAL BETA))
(PRINT (DYNAMIC-CHECKER))
(SUM ALPHA BETA)) -> :s
3 -> :s
3
-1 -> :s
-1
(PRINT (DYNAMIC-CHECKER)) -> :s
(DYNAMIC-CHECKER) -> :s
(+ ALPHA BETA) -> :s
ALPHA -> :s
3
BETA -> :s
-1
2
2
2 ; <- output
2
(SUM ALPHA BETA) <=> 2 ; <- macro expansion to 2
2 -> :s
2 ; 2 evaluates to itself
2
2
2
编译失败
但是我们无法编译您的代码:
CL-USER 10 > (compile 'test)
Error: The variable ALPHA is unbound.
1 (continue) Try evaluating ALPHA again.
2 Return the value of :ALPHA instead.
3 Specify a value to use this time instead of evaluating ALPHA.
4 Specify a value to set ALPHA to.
5 (abort) Return to level 0.
6 Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
编译器会尝试展开宏,但不会 运行 代码。由于未执行 LET
形式(编译器仅将其编译为其他内容,但不执行它),因此 alpha
.
风格
一般来说,避免在封闭代码中编写需要 运行时间状态的宏是个好主意。这样的宏只能由解释器执行。
根据用户 jkiiski 的评论,我觉得您需要了解这两个操作(宏操作和 program/function 应用程序)何时在典型的编译代码中发生。此外,我不建议您编写您定义的宏,但出于教育目的会使用它们。
让我们首先明确声明两个特殊符号,并将它们绑定到一些值:
CL-USER> (defvar alpha 6)
6
CL-USER> (defvar beta 5)
5
这不仅建立了两个变量special/dynamic,而且还分别给它们绑定了值6和5。
更重要的是,通过这样做,我们在您的宏中的示例或您的代码将被评估之前建立变量和绑定。
现在我们 运行 你的最终表格,我们应该得到以下内容:
CL-USER> (let ((alpha 3) (beta -1))
(declare (special alpha))
(declare (special beta))
(print (dynamic-checker))
(sum alpha beta))
2
11
由于 (dynamic-checker)
的打印,第一个值 2 被打印出来。返回值 11 是因为宏 sum
解析为值 11,然后有效地取代了形式 (sum alpha beta)
并在 (let ...)
的实际计算中返回代码。
要认识到的重要一点是,宏是在编译时求值的:也就是说,在编译 (let ...)
和 运行 的最终形式之前。因为它是在您的 (let ...)
表单之前评估的,所以当宏解析时,您的 let 表单不会建立 3 和 -1 与符号 alpha
和 beta
的绑定。在此之前,我很厚颜无耻地对值 6 和 5 进行了显式绑定,这就是宏所看到的。
宏解析成功后,你的let表单有效变为:
(let ((alpha 3) (beta -1))
(declare (special alpha))
(declare (special beta))
(print (dynamic-checker))
11)
这就解释了为什么我们现在没有错误,是什么导致了你之前的例子中的错误,以及为什么我们从我上面指定的添加中得到了输出,我们可以明确地看到 macro/let 形式是什么时候正在解决。