这就是为什么相互递归的宏不能以任何有用的方式工作的原因。
考虑一个系统想要评估(或编译)Lisp 代码以获得比 CL 稍微简单的 Lisp(所以我要避免 CL 中发生的一些微妙之处),例如函数的定义,需要做。它知道如何做的事情很少:
- 它知道如何调用函数;
- 它知道如何评估几种文字对象;
- 它对几种表格有一些特殊的规则——CL 称之为“特殊表格”,它(同样用 CL 来说)是汽车是特殊运算符的表格;
- 最后它知道如何查看表单是否对应于它可以调用的函数来转换它试图评估或编译的代码 - 其中一些函数是预定义的,但可以定义其他函数。
所以评估器的工作方式是遍历它需要评估的东西,寻找这些源代码转换的东西,aka 宏(最后一种情况),调用它们的函数,然后递归在结果上,直到它以没有剩余的代码结束。剩下的应该只包含前三种情况的实例,然后它知道如何处理。
所以现在考虑一下,如果评估器正在评估与名为a 的宏对应的函数的定义,它必须做什么。在 Cl-speak 中,它正在评估或编译 a 的宏函数(您可以通过 CL 中的 (macro-function 'a) 获得)。让我们假设这段代码中有一个 (b ...) 的形式,并且已知 b 也对应于一个宏。
所以在某个时候它涉及到(b ...),它知道为了做到这一点,它需要调用b 的宏函数。它绑定了合适的参数,现在它需要评估该函数主体的定义......
... 当它执行此操作时会遇到类似(a ...) 的表达式。它应该怎么做?它需要调用a 的宏函数,但它不能,因为它还不知道它是什么,因为它正在解决这个问题:它可以开始尝试再次解决它,但是这只是一个循环:它不会到达它尚未到达的任何地方。
嗯,你可以做一个可怕的把戏来避免这种情况。上面的无限回归发生是因为评估器试图提前扩展所有宏,因此递归没有基础。但是我们假设a的宏函数的定义有如下代码:
(if <something>
(b ...)
<something not involving b>)
您可以做的不是先扩展所有宏,而是在需要结果之前仅扩展您需要的宏。如果<something> 总是为假,那么你永远不需要扩展(b ...),所以你永远不会陷入这个恶性循环:递归触底。
但这意味着您必须始终按需扩展宏:您永远无法提前完成,而且由于宏扩展为源代码,您永远无法编译。换句话说,这样的策略与编译不兼容。这也意味着如果<something> 被证明是真的,那么你将再次陷入无限倒退。
请注意,这与将宏扩展为涉及相同宏的代码或扩展为使用它的代码的另一个宏完全不同。这是一个名为 et 的宏的定义,它可以做到这一点(当然不需要这样做,这只是为了看到它发生):
(defmacro et (&rest forms)
(if (null forms)
't
`(et1 ,(first forms) ,(rest forms))))
(defmacro et1 (form more)
(let ((rn (make-symbol "R")))
`(let ((,rn ,form))
(if ,rn
,rn
(et ,@more)))))
现在 (et a b c) 扩展为 (et1 a (b c)) 扩展为 (let ((#:r a)) (if #:r #:r (et b c))) (所有未实习的东西都是一样的)等等,直到你得到
(let ((#:r a))
(if #:r
#:r
(let ((#:r b))
(if #:r
#:r
(let ((#:r c))
(if #:r
#:r
t))))))
现在并非所有的非驻留符号都相同
加上let 的合理宏(let 实际上是 CL 中的一个特殊运算符),这可以进一步变成
((lambda (#:r)
(if #:r
#:r
((lambda (#:r)
(if #:r
#:r
((lambda (#:r)
(if #:r
#:r
t))
c)))
b)))
a)
这是一个“系统知道如何处理的事情”的示例:这里只剩下变量、lambda、原始条件和函数调用。
关于 CL 的好处之一是,虽然有很多有用的糖,但如果你愿意,你仍然可以在事物的内脏中四处寻找。特别是,您仍然看到宏只是转换源代码的函数。以下内容正是defmacro 版本所做的事情(不完全是:defmacro 做了必要的聪明来确保宏足够早地可用:我需要使用eval-when 来执行以下操作):
(setf (macro-function 'et)
(lambda (expression environment)
(declare (ignore environment))
(let ((forms (rest expression)))
(if (null forms)
't
`(et1 ,(first forms) ,(rest forms))))))
(setf (macro-function 'et1)
(lambda (expression environment)
(declare (ignore environment))
(destructuring-bind (_ form more) expression
(declare (ignore _))
(let ((rn (make-symbol "R")))
`(let ((,rn ,form))
(if ,rn
,rn
(et ,@more)))))))