有选择地将已评估的参数传递给宏表单的最佳做法是什么?
详细说明:宏的有用性在于它能够接收未评估的参数,这与函数表单的默认评估规则不同。但是,有一个合理的用例来评估宏参数。
考虑一个人为的例子:
(defparameter *func-body* '((print i) (+ i 1)))
假设*func-body*
可以作为宏our-defun
的主体定义为:
(defmacro our-defun (fun args &body body)
`(defun ,fun ,args ,@body))
所以在(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*))))
之后(our-defun 1)
将打印出1并按预期返回2
。我可以考虑使用eval
进行此项工作的情况,但由于其在范围界定方面的特殊性,我宁愿远离eval
。
这引发了我的问题,是否有更直接或本土的方式来做到这一点?
P.S。,
一个不那么人为的例子是函数(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))))))
答案 0 :(得分:4)
这有点困惑。宏不会收到未评估的参数。
宏获取源代码并从中创建源代码。还要记住,Lisp中的源代码实际上是作为数据提供的。宏创建代码,评估某些表单,而不是。
宏需要在编译系统中工作。在运行之前。在编译期间。所有宏看到的都是源代码,然后它从中创建源代码。将宏视为代码转换,而不是关于是否评估参数。
如果我们可以强制评估
,那就太好了*func-body*
作为宏我们定义的参数
那不是很干净。在已编译的系统中,您需要确保*func-body*
实际上具有有用的绑定,并且可以在 COMPILE TIME 时解析。
如果您有像DEFUN
这样的宏,那么让源代码保持静态是有意义的。如果你想在表单中插入一些源代码,那么在读取时这样做是有意义的:
(defun foo (i) #.`(,@*foo*))
但这是我通常希望避免的代码。
两个库宏
(ADD-HOOK)
和(REMOVE-HOOK)
,需要评估其参数。
为什么ADD-HOOK
和REMOVE-HOOK
应该是宏?如果你没有真正的理由,他们应该只是功能。因为它们使重复使用变得困难。
如果您出于某种原因想制作ADD-HOOK
和REMOVE-HOOK
个宏,那么UPDATE-HOOK
通常也应该是一个宏。
答案 1 :(得分:0)
您为宏提供的列表的格式为
(Quote (...))
因此,您实际需要的列表是您获得的列表的CADR。