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

What is the difference between macroexpand and macroexpand-1 in Clojure

我无法理解 macroexpand 和 macroexpand-1 之间的区别。

能否提供示例?

假设我们有以下代码:

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

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

(defn not-a-macro [] nil)

然后,macroexpand-1 的医生说:

If form represents a macro form, returns its expansion, else returns form.

的确如此:

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的文档:

Repeatedly calls macroexpand-1 on form until it no longer represents a macro form, then returns it.

示例:

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

发生了什么事?一旦(top-level-macro "hello")扩展为(user/inner-macro "hello"),即宏形式,macroexpand将再次进行扩展。二次展开的结果是(clojure.core/println "hello")。它不是宏形式,所以 macroexpand 就 return 就可以了。

所以,只是为了改写文档,macroexpand 将递归地进行扩展,直到 top level 表单不是宏形式。

macroexpand 的文档中还有附加说明:

Note neither macroexpand-1 nor macroexpand expand macros in subforms.

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

(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 只是 return 它而已。不要期望 macroexpand 会执行以下操作:

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

区别很简单。首先是背景:当编译器看到宏调用时,它会尝试根据其定义对其进行扩展。如果此宏生成的代码包含其他宏,它们也会被编译器扩展,依此类推,直到生成的代码完全不含宏。所以 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 然后扩展 ->