Isa*_*lla 3 lisp macros elisp elisp-macro
假设我定义
(defmacro macro-print (str)
  (print "hello"))
并运行
(let ((i 0))
  (while (< i 3)
    (macro-print i)
    (setq i (+ 1 i))))
输出是
"hello"
nil
为什么是这样?
我本来期待看到3hello秒。这是因为,根据手册,while特殊形式首先评估其条件,如果评估结果为非零,则评估其主体中的形式。由于条件适用于 的三个值i,因此我希望对(macro-print i)表单进行 3 次评估。每次展开时,它都应该打印“hello”。然而,解释器似乎(macro-print i)在第一次评估时用它的扩展替换(因为 print 返回传递给它的字符串,它会用“hello”替换它)。为什么会发生这种情况?
我已经知道正确的编写方法macro-print是 return (list 'print ,i),但事实上我无法预测循环的实际行为,这表明我对我想要纠正的 lisp 模型存在误解。
宏本质上是其域和范围是源代码的函数。在 CL 中,它们实际上是这样的:宏是由一个函数实现的,该函数的参数是源代码(和环境对象)并返回其他源代码。我认为 elisp 中的实现不太明确。
由于此类宏在代码求值期间至少被扩展一次(在 CL 中,这意味着调用实现宏的函数),以便它们可以返回其扩展。它们可以仅扩展一次(并且,就效率而言,这显然是理想的),或者不止一次,但它们至少扩展一次。一般来说,这是您拥有的唯一保证。
对于交互式解释器来说,如果每次更改引用宏的代码或更改宏本身时都扩展宏,那显然会很好。这可以通过将宏扩展为解释器的一部分来实现,至少一次。
您所看到的是,在某些情况下,elisp 解释器只会扩展宏一次。
在编译实现的情况下,读取源代码后评估是一个两阶段的过程:源代码在编译时转换为某种编译表示,并在运行时执行该编译表示。宏扩展将在编译期间发生,而不是在执行期间发生。
事实上,CL 强制执行了这一点:部分定义了最小编译,以便
正在编译的源代码中出现的所有宏和符号宏调用都会在编译时展开,这样它们在运行时就不会再次展开。
如果您想要在运行时执行的代码,请使用函数,或者,如果您需要宏来扩展语言,请使该代码成为宏扩展的一部分:它返回的值的一部分,而不是副作用扩大它。