动态绑定和宏

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 首先根据两个变量 alphabeta 求值,这两个变量作为符号发送到宏中。因此,宏中要计算的代码是:

(+ 'alpha 'beta)      

这行不通,因为我们不能添加两个符号。
2. 变量alphabeta词法绑定,因此,宏的代码无法访问[=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 将评估提供给它的符号的值。如果它在符号的范围内,它只能这样做。因此,为了做到这一点,我们可以使 alphabeta 动态绑定。
此外,为了检查动态绑定是否有效,我们可以创建一个函数 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 返回的,它添加了 alphabeta,即使它们不是它的参数,这证明了变量 alphabeta 可以被 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 步进器,它使用解释器。 :sstep 命令。

(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 与符号 alphabeta 的绑定。在此之前,我很厚颜无耻地对值 6 和 5 进行了显式绑定,这就是宏所看到的。

宏解析成功后,你的let表单有效变为:

(let ((alpha 3) (beta -1))
  (declare (special alpha))
  (declare (special beta))
  (print (dynamic-checker))
  11)

这就解释了为什么我们现在没有错误,是什么导致了你之前的例子中的错误,以及为什么我们从我上面指定的添加中得到了输出,我们可以明确地看到 macro/let 形式是什么时候正在解决。