1

考虑以下代码:

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. 变量alphaandbeta词法绑定的,因此宏的代码不能访问alphaand的符号值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,它添加alphabeta,即使它们不是它的参数,这证明变量alphabeta可以被 的成员动态访问let
因此,宏sum现在应该可以工作了,因为之前发生的两个问题都得到了解决。
但这显然不是这种情况,我错过了一些东西。
任何帮助表示赞赏。

4

2 回答 2

4

解释器和编译器与交互性

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 (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.

风格

通常最好避免编写需要来自封闭代码的运行时状态的宏。这样的宏只能由解释器执行。

于 2017-06-17T08:18:05.527 回答
2

根据用户 jkiiski 的评论,我觉得您需要了解这两个操作(宏操作和程序/功能应用程序)何时在通常编译的代码中发生。此外,我不建议编写您定义的宏,但会出于教育目的使用它们。

让我们首先明确声明两个符号是特殊的,并将它们绑定到一些值:

CL-USER> (defvar alpha 6)
6
CL-USER> (defvar beta 5)
5

这不仅将两个变量建立为特殊/动态,而且还分别将它们绑定到值 6 和 5。

更重要的是,通过这样做,我们在您的宏中的示例或您的代码将被评估之前建立变量和绑定。

现在我们运行您的最终表单,我们应该得到以下信息:

CL-USER> (let ((alpha 3) (beta -1))
           (declare (special alpha))
           (declare (special beta))
           (print (dynamic-checker))
           (sum alpha beta))

2
11

由于打印了 2,所以打印了第一个值 2 (dynamic-checker)。11 的值被返回,因为宏sum正在解析为值 11,然后它有效地取代了表单(sum alpha beta)并在代码的实际评估中返回(let ...)

要意识到的重要一点是宏在编译时被评估:也就是说,在最终形式的(let ...)编译和运行之前。因为它是在您的(let ...)表单之前评估的,所以您的 let 表单不会在宏解析时建立 3alpha和-1 到符号的绑定。beta在此之前,我一直厚颜无耻地对值 6 和 5 进行了显式绑定,这就是宏所看到的。

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

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

这就解释了为什么我们现在没有错误,是什么导致了您之前的示例中的错误,以及为什么我们从上面指定的添加中获得了输出,并且我们可以明确地看到宏/let 表单何时被解析。

于 2017-06-17T08:40:40.680 回答