Lisp递归宏问题

时间:2013-05-09 03:44:59

标签: recursion macros lisp common-lisp

我正在编写一个递归的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))))

2 个答案:

答案 0 :(得分:9)

要回答您的第一个问题,您将拥有一个永不停止递归的递归宏扩展。 if的存在并不会停止递归宏扩展,因为宏扩展发生在编译时,而if发生在运行时。

要回答第二个问题,您不能使用flet指定递归函数,而是必须使用labels

答案 1 :(得分:7)

由于Common Lisp中的宏扩展在运行之前发生,所以这有点棘手。

请记住,宏看到了源代码。这意味着:

  • 使用宏时,数字n必须作为数字而不是变量传递。因此,在宏观扩展时,数量是已知的。对于这样的宏,我会在宏中检查 - 否则你总是想写一些像(let ((n 10)) (n-times n ...))这样的东西 - 这是行不通的。

  • 宏需要计算递归迭代。因此逻辑在宏中,而不在生成的代码中。每个宏都需要生成代码,这在宏扩展时更简单一步 - 直到达到基本情况。