动态绑定和宏



请考虑以下代码:

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词法绑定的,因此宏的代码无法访问alphabeta的符号值。
因此,重新定义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 步进器,它使用解释器。: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没有绑定。

风格

通常,最好避免从封闭代码编写需要运行时状态的宏。此类宏只能由解释器执行。

根据用户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

由于打印了(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)

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

相关内容

  • 没有找到相关文章

最新更新