我不能谈论强大的因为它可能有点主观,但宏是处理 Lisp 数据的常规 Lisp 函数,所以它们就像富有表现力的作为其他功能。其他语言中的模板或通用函数不是这种情况,它们更多地依赖静态类型并且(有意)受到更多限制。
在某种程度上,是的,宏是简单的句法工具,但您的仿真重点是宏的动态语义,即。如何运行在运行时计算宏的代码。然而:
- 使用
eval的代码不等同于扩展代码
- 宏的预处理/编译时方面未被模拟
词法范围
函数,如+,不继承词法作用域:
(let ((x 30))
(+ 3 4))
在+ 的定义中,您无法访问x。能够做到这一点就是“动态范围”的意义所在(更准确地说,请参见动态范围,无限范围变量)。但是现在依赖动态作用域是个例外。大多数函数使用词法作用域,eval 也是如此。
eval 函数计算无效的词法环境,它永远无法访问周围的词法绑定。因此,它的行为类似于任何常规函数。
因此,在您的示例中,在转换后的源代码上调用 eval 将不起作用,因为 arg1 到 argnN 可能未绑定(这取决于您的宏的作用)。
为了具有等效的形式,您必须在转换后的代码中注入绑定,或者在更高级别进行扩展:
(defun expand-square (var)
(list '* var var))
;; instead of:
(defun foo (x) (eval (expand-square 'x))) ;; x unbound during eval
;; inject bindings
(defun foo (x) (eval `(let ((z ,x)) (expand-square z))))
;; or expand the top-level form
(eval `(defun foo (x) ,(expand-square 'x)))
请注意,宏也可以通过其 lambda 列表中的 &environment 参数访问词法环境。此环境的使用依赖于实现,但可用于访问与变量关联的声明,例如。
还要注意在最后一个例子中你是如何评估代码的定义函数,而不是什么时候跑步它。这是关于宏的第二件事。
扩张时间
为了模拟宏,您可以通过在运行时模拟它的形式在本地替换对宏的调用(使用 let 捕获您希望在扩展代码中看到的所有绑定,这很乏味),但是您会错过了宏的有用方面,即:提前生成代码。
上面的最后一个示例显示了如何引用 defun 并将其包装在 eval 中,如果您想模拟宏完成的预处理工作,基本上您需要对所有函数都这样做。
宏系统是一种以易于使用的方式将此预处理步骤集成到语言中的方法。
结论
当函数不能时,宏本身是抽象事物的好方法。例如,您可以使用更人性化、更稳定的语法来隐藏实现细节。这就是您在 Common Lisp 中定义模式匹配能力的方式,使它们看起来像是语言的一部分,而没有太多的运行时惩罚或冗长。
它们依赖于语言中集成的简单术语重写函数,但您可以根据需要在编译时或运行时自己模拟它们的行为。它们可用于执行不同类型的抽象,这些抽象通常在其他语言中缺失或更麻烦,但也有局限性:它们自己不“理解”代码,它们不提供访问所有功能的权限编译器(类型传播等),但它们是可移植的。