我正在编写一个递归的Lisp宏,它接受一个 n 数字并评估主体 n 次(来自ANSI Lisp的练习)。我已经尝试了两种方法 - 在扩展中使用宏调用,并将宏扩展为本地递归函数。两者都不能按我的意愿行事。
这是第一个 - 它有一个堆栈溢出,但是当我使用macroexpand-1查看它的扩展时,我似乎很好。
(defmacro n-times (n &rest body)
(let ((i (gensym)))
`(let ((,i ,n))
(if (zerop ,i) nil
(progn
,@body
(n-times (- ,i 1) ,@body))))))
这是第二个 - 它出错了,“未定义函数#xxxx用参数(z)调用”,其中#xxxx是gensym的名称,z比我调用它的数字少1。我认为我使用gensyms和flet的方式存在问题,但我不确定如何正确地执行此操作。
(defmacro n-times (n &rest body)
(let ((g (gensym)))
`(flet ((,g (x)
(if (zerop x) nil
(progn
,@body
(,g (- x 1))))))
(,g ,n))))
答案 0 :(得分:9)
要回答您的第一个问题,您将拥有一个永不停止递归的递归宏扩展。 if
的存在并不会停止递归宏扩展,因为宏扩展发生在编译时,而if
发生在运行时。
要回答第二个问题,您不能使用flet
指定递归函数,而是必须使用labels
。
答案 1 :(得分:7)
由于Common Lisp中的宏扩展在运行之前发生,所以这有点棘手。
请记住,宏看到了源代码。这意味着:
使用宏时,数字n必须作为数字而不是变量传递。因此,在宏观扩展时,数量是已知的。对于这样的宏,我会在宏中检查 - 否则你总是想写一些像(let ((n 10)) (n-times n ...))
这样的东西 - 这是行不通的。
宏需要计算递归迭代。因此逻辑在宏中,而不在生成的代码中。每个宏都需要生成代码,这在宏扩展时更简单一步 - 直到达到基本情况。