这个宏的递归定义可以实现它的应用(将整数从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是一个宏,所以宏评估应该变成一个无限循环。编译器是否创建了递归函数?如果这个定义创建了一个递归函数,有没有办法以递归方式定义宏?
(为简洁起见,这是一个愚蠢的例子,一个功能当然会更好地为此工作)
答案 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并且计算可以使用此结果并将其他术语添加到其中。解释器获取代码,评估它的内容,并在必要时进行宏扩展。然后评估生成的代码或宏扩展。等等。
现在让我们看一下编译器。
(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)
没有。