【问题标题】:How do recursive macro definitions get evaluated如何评估递归宏定义
【发布时间】:2012-01-04 06:00:24
【问题描述】:

这个宏的递归定义做了它应该做的(从 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 是一个宏,所以宏求值应该变成一个无限循环。编译器会创建一个递归函数吗?如果这个定义创建了一个递归函数,有没有办法递归地定义宏?

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

【问题讨论】:

    标签: recursion macros lisp common-lisp


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      对于 Kilan 的回答,我要补充一点,macroexpand 不必在表单中生成所有宏的完全扩展,直到没有剩余宏 :) 如果您查看 Hyperspec,您您会看到,它会评估 整个 表单,直到它不是宏(在您的情况下,它会在 if 处停止)。并且在编译过程中,所有的宏都被展开,就像macroexpand 被应用到源代码树的每个元素,而不仅仅是它的根。

      【讨论】:

        【解决方案3】:

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

        【讨论】:

        • 谢谢,正是我正在寻找的那种信息
        【解决方案4】:

        你的例子不起作用。

        它可以在解释器中工作。但是使用编译器,您会在编译过程中看到无限循环。

        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)) 等等。最后你会看到堆栈溢出。

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

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

        【讨论】:

          【解决方案5】:

          这是一个有效的实现:

          (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)
          

          没有。

          【讨论】:

            猜你喜欢
            • 2020-07-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-01-03
            • 1970-01-01
            • 2020-09-01
            • 2020-01-26
            相关资源
            最近更新 更多