背景杂乱无章
您所拥有的是非常晚的绑定宏。这是一种可行的方法,但效率低下,因为重复执行相同的代码会重复扩展宏。
从积极的方面来说,这对交互式开发很友好。如果程序员改变了一个宏,然后重新调用了一些使用它的代码,比如之前定义的函数,新的宏会立即生效。这是一种直观的“按我的意思去做”的行为。
在较早扩展宏的宏系统下,当宏发生变化时,程序员必须重新定义所有依赖于宏的函数,否则现有定义将继续基于旧的宏扩展,忽略新版本的宏.
一个合理的方法是让这个后期绑定宏系统用于解释代码,但一个“常规”(因为没有更好的词)宏系统用于编译代码。
扩展宏不需要单独的环境。它不应该,因为本地宏应该与变量在同一个命名空间中。例如,在 Common Lisp 中,如果我们这样做(let (x) (symbol-macrolet ((x 'foo)) ...))
,内部符号宏会隐藏外部词法变量。宏扩展器必须知道变量绑定形式。反之亦然!let
如果变量有一个内部x
,它会影响一个外部symbol-macrolet
。宏扩展器不能盲目地替换x
正文中出现的所有事件。所以换句话说,Lisp 宏扩展必须知道宏和其他类型的绑定共存的完整词法环境。当然,在宏扩展期间,您不会以相同的方式实例化环境。当然,如果有(let ((x (function)) ..)
,(function)
没有被调用,x
也没有被赋予一个值。但是宏扩展器知道x
在这个环境中有一个,所以出现的x
不是宏。
因此,当我们说一个环境时,我们真正的意思是统一环境有两种不同的表现形式或实例化:扩展时间表现形式和评估时间表现形式。后期绑定宏通过将这两个时间合并为一个来简化实现,就像您所做的那样,但不必如此。
另请注意,Lisp 宏可以接受&environment
参数。如果宏需要调用macroexpand
用户提供的某些代码,则需要这样做。这种通过宏递归回到宏扩展器必须传递正确的环境,以便用户的代码可以访问其词法周围的宏并正确扩展。
具体例子
假设我们有这样的代码:
(symbol-macrolet ((x (+ 2 2)))
(print x)
(let ((x 42)
(y 19))
(print x)
(symbol-macrolet ((y (+ 3 3)))
(print y))))
这对打印的影响4
,42
和6
。让我们使用 Common Lisp 的 CLISP 实现,并使用 CLISP 的特定于实现的函数来扩展它system::expand-form
。我们不能使用常规的标准,macroexpand
因为它不会递归到本地宏中:
(system::expand-form
'(symbol-macrolet ((x (+ 2 2)))
(print x)
(let ((x 42)
(y 19))
(print x)
(symbol-macrolet ((y (+ 3 3)))
(print y)))))
-->
(LOCALLY ;; this code was reformatted by hand to fit your screen
(PRINT (+ 2 2))
(LET ((X 42) (Y 19))
(PRINT X)
(LOCALLY (PRINT (+ 3 3))))) ;
(首先,关于这些locally
表单。为什么它们在那里?请注意,它们对应于我们有 a 的地方symbol-macrolet
。这可能是为了声明。如果symbol-macrolet
表单的主体有声明,它们必须限定在该主体, 并且locally
会这样做。如果扩展symbol-macrolet
没有留下这个locally
包装,那么声明将有错误的范围。)
从这个宏展开你可以看到任务是什么。宏扩展器必须遍历代码并识别所有绑定构造(实际上是所有特殊形式),而不仅仅是与宏系统有关的绑定构造。
注意其中一个实例(print x)
是如何被单独留下的:在 . 范围内的实例(let ((x ..)) ...)
。另一个变成(print (+ 2 2))
了,按照符号宏换x
。
我们可以从中学到的另一件事是宏扩展只是替换扩展并删除了symbol-macrolet
形式。所以剩下的环境是原来的环境,减去在膨胀过程中被擦掉的所有宏观材料。宏扩展在一个大的“大统一”环境中尊重所有词汇绑定,但随后优雅地蒸发,只留下类似的代码(print (+ 2 2))
和其他痕迹(locally ...)
,只有非宏绑定构造导致减少版本原来的环境。
因此,现在在评估扩展代码时,只有简化环境的运行时特性开始发挥作用。let
绑定被实例化并填充初始值等。在扩展期间,这些都没有发生;非宏绑定只是在那里声明它们的范围,并暗示运行时未来的存在。