如何评估递归宏定义

时间:2011-11-22 21:05:27

标签: recursion macros lisp common-lisp

这个宏的递归定义可以实现它的应用(将整数从1加到n):

(defmacro sum-int-seq (n)
  `(cond
     ((equal 0 ,n) 0)
     (t (+ ,n (sum-int-seq (- ,n 1))))))

例如(sum-int-seq 5)给出15。

但为什么会这样呢?当宏扩展时,我得到了这个:

(macroexpand '(sum-int-seq 5))
(IF (EQUAL 0 5) 0 (+ 5 (SUM-INT-SEQ (- 5 1))))

但是因为sum-int-seq是一个宏,所以宏评估应该变成一个无限循环。编译器是否创建了递归函数?如果这个定义创建了一个递归函数,有没有办法以递归方式定义宏?

(为简洁起见,这是一个愚蠢的例子,一个功能当然会更好地为此工作)

5 个答案:

答案 0 :(得分:14)

您的示例不起作用。

它可能在翻译中起作用。但是使用编译器,你会在编译过程中看到无限循环。

CL-USER 23 > (defun test (foo)
                (sum-int-seq 5))
TEST

让我们使用LispWorks解释器:

CL-USER 24 > (test :foo)
15

让我们尝试编译函数:

CL-USER 25 > (compile 'test)

Stack overflow (stack size 15997).
  1 (continue) Extend stack by 50%.
  2 Extend stack by 300%.
  3 (abort) Return to level 0.
  4 Return to top loop level 0.

Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.

那么,现在接下来的问题是:为什么它在解释器中有效,但是编译器无法编译它?

好的,我会解释一下。

让我们先看看口译员。

  • 它看到(sum-int-seq 5)
  • 它将宏扩展到(COND ((EQUAL 0 5) 0) (T (+ 5 (SUM-INT-SEQ (- 5 1)))))
  • 然后评估上面的表格。它确定需要计算(+ 5 (SUM-INT-SEQ (- 5 1)))。为此,它需要宏扩展(SUM-INT-SEQ (- 5 1))
  • 最终会扩展为类似(cond ((EQUAL 0 (- (- (- (- (- 5 1) 1) 1) 1) 1)) 0) ...的内容。然后返回0并且计算可以使用此结果并将其他术语添加到其中。

解释器获取代码,评估它的内容,并在必要时进行宏​​扩展。然后评估生成的代码或宏扩展。等等。

现在让我们看一下编译器。

  • 它看到(sum-int-seq 5)并将其扩展为(COND ((EQUAL 0 5) 0) (T (+ 5 (SUM-INT-SEQ (- 5 1)))))
  • 现在宏扩展将在子表单上完成。
  • 编译器将宏扩展(SUM-INT-SEQ (- 5 1))。请注意,代码永远不会被评估,只会被扩展。
  • 编译器将宏扩展(SUM-INT-SEQ (- (- 5 1) 1))等等。最后你会看到堆栈溢出。

编译器遍历(递归编译/扩展)代码。它可能不会执行代码(除非它进行优化或宏实际上明确地评估它)。

对于递归宏,您需要实际倒计时。如果你在宏内部进行评估,那么像(sum-int-seq 5)这样的东西就可以了。但对于(defun foo (n) (sum-int-seq n)),这是没有希望的,因为编译器不知道n的值是什么。

答案 1 :(得分:3)

要添加的另一件事:在您的示例中,宏内部sum-int-seq的出现位于带引号的表达式中,因此在评估宏时不会扩展它。在调用宏之前,它只是数据。并且因为它嵌套在cond内,所以在运行时内部宏仅在条件为真时被调用,与常规函数中的相同。

答案 2 :(得分:2)

扩展宏会生成Lisp代码,然后对其进行评估。调用函数会将执行流转移到预先存在的lisp代码的副本,然后运行该代码。除此之外,两者非常相似,并且递归以相同的方式工作。特别是,宏扩展停止的原因与正确写入的递归函数停止的原因相同:因为存在终止条件,并且已经写入了一个调用和下一个调用之间的转换,以便实际达到此条件。如果没有达到,宏扩展将进入一个循环,就像一个不正确编写的递归函数。

答案 3 :(得分:2)

对于我想补充的Kilan的答案,macroexpand不必生成表单中所有宏的完全扩展,直到没有宏为止:)如果你看{{3} },你会看到它评估整个表单,直到它不是一个宏(在你的情况下它停在if)。在编译期间,所有宏都会被扩展,就好像macroexpand被应用于源树的每个元素,而不仅仅是它的根。

答案 4 :(得分:2)

这是一个有效的实现:

(defmacro sum-int-seq (n)
  (cond
     ((equal 0 n) `0)
     (t `(+ ,n (sum-int-seq ,(- n 1))))))

可以编写一个递归宏,但是(如上所述),扩展必须能够在编译时达到基本情况。因此,在编译时必须知道传递给宏的所有参数的值。

(sum-int-seq 5)

工作,但

(sum-int-seq n)

没有。