有没有办法让宏在返回结果之前进行额外的评估?

Is there a way to get a macro to do an extra evaluation before returning its result?

我试图让我的宏在 returning 之前对其结果进行额外的评估。没有 eval 可以完成吗?

我正在尝试解决下面练习 4 中的问题:

  1. Define a macro nth-expr that takes an integer n and an arbitrary number of expressions, evaluates the nth expression and returns its value. This exercise is easy to solve, if you assume that the first argument is a literal integer.

4. As exercise 3, but assume that the first argument is an expression to be evaluated.

让宏选择正确的表达式很容易:

(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) 是我们想要的,但我们希望宏在 return 对其进行计算之前将其计算为 4。

当然可以用eval来完成:

(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 的调用将 return (+ 3 1),然后这将是对 nth-expr 调用的扩展,在 运行-time 将计算为 4。当然,它会爆炸,因为 NES 被视为文字。

没那么简单。

使用 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 可能会要求您考虑 case 语句的工作原理。也就是说,在评估关键字形式(即第一个参数)之后,它将依次与每个子句中的关键字进行比较,直到找到匹配项。如果有很多子句,比较可能会很耗时。你可以通过 仔细 阅读 Hyperspec 中 case 的条目来发现这一点(因为他不止一次坚持我这样做):

The keyform or keyplace is evaluated to produce the test-key. Each of the normal-clauses is then considered in turn.

另请注意,构造许多 case 子句会增加编译时扩展和编译宏的时间。

关于你在nth-expr%%中使用eval,你仍然可以通过切换到apply来达到eval的效果:

(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 处的堵漏以了解更强大的处理方法。

一般来说,更有效的处理表达式的方法是作为一个简单的向量,而不是一个列表。 (问题陈述不排除向量表示。)虽然 nthcase 涉及搜索表达式 one-by-one,但函数如 arefsvref可以直接索引进去。假设表达式向量连同索引一起传递给宏,如果是列表,可能首先需要 (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