Lisp递归宏问题

lig*_*ike 2 lisp macros recursion 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))))))
Run Code Online (Sandbox Code Playgroud)

这是第二个 - 它出错了,"未定义函数#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))))
Run Code Online (Sandbox Code Playgroud)

Chr*_*ung 9

要回答你的第一个问题,你有一个永远不会停止递归的递归宏扩展.if由于宏扩展发生在编译时,并且您if在运行时发生,因此存在并不会停止递归宏扩展.

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

  • `flet`使绑定仅在体内可见.`labels`也使绑定在绑定定义中可见. (2认同)

Rai*_*wig 7

由于Common Lisp中的宏扩展在运行时之前发生,因此这有点棘手.

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

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

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