Clojure中macroexpand和macroexpand-1有什么区别?

10

我不太明白macroexpand和macroexpand-1之间的区别。

你能举个例子吗?

2个回答

16

假设我们有以下代码:

(defmacro inner-macro [arg]
  `(println ~arg))

(defmacro top-level-macro [arg]
  `(inner-macro ~arg))

(defn not-a-macro [] nil)

然后,macroexpand-1 的文档说:

如果表单表示一个宏形式,则返回其展开,否则返回表单本身。

确实如此:

user> (macroexpand-1 '(inner-macro "hello"))
(clojure.core/println "hello")

user> (macroexpand-1 '(top-level-macro "hello"))
(user/inner-macro "hello")

user> (macroexpand-1 '(not-a-macro))
(not-a-macro)

换句话说,如果提供的表达式是宏表达式,macroexpand-1 只会展开一步。

然后,macroexpand 的文档如下:

反复调用 macroexpand-1 直到表单不再表示为宏表单,然后返回表单。

例子:

user> (macroexpand '(top-level-macro "hello"))
(clojure.core/println "hello")

发生了什么?一旦(top-level-macro "hello")扩展为(user/inner-macro "hello"),这是宏形式,macroexpand将再次执行扩展。第二次扩展的结果是(clojure.core/println "hello")。这不是宏形式,因此macroexpand只是返回它。

因此,简单地重述文档,macroexpand会递归进行扩展,直到顶层形式不再是宏形式为止。

此外,在 macroexpand 的文档中还有其他注释:

注意,macroexpand-1 和 macroexpand 都不会扩展子表中的宏。

这是什么意思?假设我们有另一个宏:

(defmacro subform-macro [arg]
  `(do
     (inner-macro ~arg)))

让我们试着扩展它:

user> (macroexpand-1 '(subform-macro "hello"))
(do (user/inner-macro "hello"))

user> (macroexpand '(subform-macro "hello"))
(do (user/inner-macro "hello"))

由于 (do ...) 表单不是宏,因此 macroexpand-1macroexpand 只会返回它本身而已,不要期望 macroexpand 会做以下操作:

user> (macroexpand '(subform-macro "hello"))
(do (clojure.core/println "hello"))

7
区别相当简单,首先是背景:当编译器看到宏调用时,它会根据其定义来展开它。如果由此宏生成的代码包含其他宏,则编译器也会展开它们,依此类推,直到生成的代码完全不含宏。因此,macroexpand-1仅扩展顶级宏并显示结果(无论是否生成另一个宏调用),而macroexpand则尝试按照编译器的流水线进行扩展(部分地,在子表单中不展开宏。要进行完整的扩展,您应该查看clojure.walk/maxroexpand-all)。
小例子:
user> (defmacro dummy [& body]
        `(-> ~@body))
#'user/dummy

这个愚蠢的宏生成了对另一个宏(->)的调用。
user> (macroexpand-1 '(dummy 1 (+ 1)))
(clojure.core/-> 1 (+ 1))

macroexpand-1只展开dummy,但保持->未展开

user> (macroexpand '(dummy 1 (+ 1)))
(+ 1 1)

macroexpand 会扩展 dummy,然后再扩展 ->


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接