Wei*_* 彭巍 6 macros common-lisp
有选择地将评估参数传递给宏表单的最佳做法是什么?
详细说明:宏的用处在于它能够接收未评估的参数,这与函数形式的默认评估规则不同。但是,存在用于评估宏参数的合法用例。
考虑一个人为的例子:
(defparameter *func-body* '((print i) (+ i 1)))
Run Code Online (Sandbox Code Playgroud)
假设*func-body*
可以作为our-defun
定义为的宏的主体会很好:
(defmacro our-defun (fun args &body body)
`(defun ,fun ,args ,@body))
Run Code Online (Sandbox Code Playgroud)
所以之后(our-defun foo (i) (+ 1 i))
,我们可以说(foo 1)
得到2
。但是,如果我们使用(our-defun foo (i) *func-body*)
, 的结果(foo 1)
将是((PRINT I) (+ I 1))
(即 的值*func-body*
)。如果我们可以强制将 的评估*func-body*
作为宏的参数,那就太好了our-defun
。
目前,我可以想到一种使用compile
和funcall
执行此操作的技术,例如
(funcall (compile nil `(lambda () (our-defun foo (i) ,@*func-body*))))
Run Code Online (Sandbox Code Playgroud)
之后(our-defun 1)
将按2
预期打印出 1 并返回。我可以想到使用 来完成这项工作eval
,但eval
由于它在范围界定方面的特殊性,我宁愿远离。
这导致了我一开始的问题,有没有更直接或更原生的方法来做到这一点?
附注,
甲不那么人为的例子是在函数(UPDATE-HOOK)
,它使用两个库宏(ADD-HOOK)
和(REMOVE-HOOK)
,需要评估其参数。的(funcall (compile nil `(lambda () ...)))
上述技术被用在这里。
(defun update-hook (hook hook-name &optional code)
(funcall (compile nil `(lambda () (remove-hook ,hook ',hook-name))))
(unless (null code)
(compile hook-name `(lambda () ,@code))
(funcall (compile nil `(lambda () (add-hook ,hook ',hook-name))))))
Run Code Online (Sandbox Code Playgroud)
这就有点糊涂了。宏不会接收未评估的参数。
宏获取源代码并从中创建源代码。还要记住,Lisp 中的源代码实际上是作为数据提供的。宏创建代码,它评估一些表格,有些不评估。
宏需要在编译系统中工作。在运行之前。在编译期间。所有宏看到的是源代码,然后它从中创建源代码。将宏视为代码转换,而不是评估参数与否。
如果我们可以强制将 的评估
*func-body*
作为宏 our-defun 的参数,那就太好了
那不是很干净。在编译系统中,您需要确保它*func-body*
实际上具有有用的绑定,并且可以在编译时解析。
如果您有一个像 的宏DEFUN
,那么将源代码设为静态是有意义的。如果你想在表单中插入一些源代码,那么在阅读时这样做是有意义的:
(defun foo (i) #.`(,@*foo*))
Run Code Online (Sandbox Code Playgroud)
但这是我通常想要避免的代码。
两个库宏
(ADD-HOOK)
和(REMOVE-HOOK)
和需要评估其参数。
为什么应该ADD-HOOK
和REMOVE-HOOK
是宏?如果你没有真正的理由,它们应该只是函数。已经因为它们使重用变得困难。
如果您出于某种原因想要制作ADD-HOOK
和REMOVE-HOOK
宏,那么UPDATE-HOOK
通常也应该是一个宏。