我试图让我的宏对结果进行额外评估,然后再返回结果。如果没有eval
?
我正在尝试在下面的练习4中解决问题:
- 定义一个宏观nth-Expr,该宏nth-expr采用整数n和任意数量的表达式,评估nth表达式并返回其值。如果您认为第一个论点是字面整数。
,此练习很容易解决。4。作为练习3,但假设第一个参数是要评估的表达式。
很容易获得宏来选择正确的表达:
(defmacro nth-expr% (n &rest es)
`(nth ,n ',es))
CL-USER> (defvar i 1)
I
CL-USER> (nth-expr% (1+ i) (+ 2 3) (- 4 3) (+ 3 1))
(+ 3 1)
表达式(+ 3 1)
是我们想要的,但是我们希望宏将其评估为4。
当然可以通过评估来完成:
(defmacro nth-expr%% (n &rest es)
`(eval (nth ,n ',es)))
CL-USER> (nth-expr%% (1+ i) (+ 2 3) (- 4 3) (+ 3 1))
4
但是还有另一种方法吗?
感觉解决方案应该是将nth-expr%
的主体放在辅助宏中,并且顶级宏仅包含对此助手的无引用的呼叫:
(defmacro helper (n es)
`(nth ,n ',es))
(defmacro nth-expr (n &rest es) ; doesn't work!
(helper n es))
的想法是,呼叫helper
将返回(+ 3 1)
,这将是调用nth-expr
的呼叫的扩展,在运行时会评估4.当然会爆炸,因为N
和ES
获取被视为文字。
这并不容易。
使用eval
不好,因为eval
不评估本地词汇环境中的代码。
请记住,如果我们允许评估表达式以确定要执行的另一个表达式的数量,那么我们在宏扩展时间不知道此数字 - 因为该表达式可以基于需要计算的值 - 例如基于某些变量:
(nth-expression
foo
(bar)
(baz))
所以我们可能想考虑执行此操作的代码:
(case foo
(0 (bar))
(1 (baz)))
CASE
正在评估foo
,然后使用结果来找到其头部具有相同值的子句。然后将评估该条款的结果形式。
现在我们需要编写将前者扩展到后者的代码。
这将是一个非常简单的版本:
(defmacro nth-expression (n-form &body expressions)
`(case ,n-form
,@(loop for e in expressions
and i from 0
collect `(,i ,e))))
问题:使用CASE
的缺点可能是什么?
knuto:Rainer Joswig可能会要求您考虑案件陈述的工作原理。也就是说,在评估密钥形式(即,第一个参数(之后,将将其顺序比较与每个子句中的密钥进行比较,直到找到匹配为止。如果有很多条款,则可能会耗时。您可以通过仔细在HypersPec中读取case
的条目(因为他不止一次地坚持我愿意(:
评估密钥形式或密钥位置以产生测试键。每一个 然后依次考虑正常示例。
还要注意,构建许多case
条款将增加在编译时扩展和编译宏的时间。
关于您在nth-expr%%
中使用eval
,您仍然可以通过切换到apply
来实现评估的效果:
(defmacro nth-expr%% (n &rest es)
`(let ((ne (nth ,n ',es)))
(apply (car ne) (cdr ne))))
但请参阅http://www.gigamonkeys.com/book/macros-defining-your-own.html上的泄漏。
通常,处理表达式的一种更有效的方法是简单的向量,而不是列表。(问题语句不排除向量表示。(虽然nth
和case
涉及一单个表达式搜索,但诸如aref
或svref
之类的函数可以直接索引中。假设表达式向量与索引一起传递给宏,也许首先需要 (coerce expressions 'simple-vector)
,如果列表,则可以在恒定时间内计算结果,无论有多少个表达式:
(defmacro nth-expr%%% (n es)
`(let ((ne (svref ',es ,n)))
(apply (car ne) (cdr ne))))
现在
(defvar i 1)
(nth-expr%%% (1+ i) #((+ 2 3) (- 4 3) (+ 3 1))) -> 4