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

Ert*_*tin 8 lisp macros functional-programming clojure

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

你能提供一些例子吗?

Ole*_*Cat 11

假设我们有以下代码:

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

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

(defn not-a-macro [] nil)
Run Code Online (Sandbox Code Playgroud)

然后,doc macroexpand-1说:

如果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)
Run Code Online (Sandbox Code Playgroud)

换句话说,macroexpand-1如果提供的形式是宏形式,那么宏扩展只需要一步.

然后,doc macroexpand:

在窗体上重复调用macroexpand-1,直到它不再表示宏窗体,然后返回它.

例:

user> (macroexpand '(top-level-macro "hello"))
(clojure.core/println "hello")
Run Code Online (Sandbox Code Playgroud)

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

因此,只是为了重新定义doc,macroexpand将以递归方式进行扩展,直到顶级表单不是宏形式.

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

请注意,macroexpand-1和macroexpand都不会在子窗体中展开宏.

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

(defmacro subform-macro [arg]
  `(do
     (inner-macro ~arg)))
Run Code Online (Sandbox Code Playgroud)

让我们尝试扩展它:

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

user> (macroexpand '(subform-macro "hello"))
(do (user/inner-macro "hello"))
Run Code Online (Sandbox Code Playgroud)

因为,(do ...)form不是一个宏macroexpand-1macroexpand只是返回它而已.不要指望macroexpand会做以下事情:

user> (macroexpand '(subform-macro "hello"))
(do (clojure.core/println "hello"))
Run Code Online (Sandbox Code Playgroud)


lee*_*ski 5

区别很简单。首先是背景:当编译器看到宏调用时,它将尝试根据其定义对其进行扩展。如果此宏生成的代码包含其他宏,它们也将由编译器扩展,依此类推,直到生成的代码完全没有宏为止。因此,macroexpand-1仅扩展最上面的宏并显示结果(无论它是否生成另一个宏调用),同时macroexpand尝试遵循编译器的管道(部分地,不要以子形式扩展宏。要进行完整的扩展,您应该看一下clojure.walk/maxroexpand-all)。

小例子:

user> (defmacro dummy [& body]
        `(-> ~@body))
#'user/dummy
Run Code Online (Sandbox Code Playgroud)

这个愚蠢的宏会生成对另一个宏(->)的调用

user> (macroexpand-1 '(dummy 1 (+ 1)))
(clojure.core/-> 1 (+ 1))
Run Code Online (Sandbox Code Playgroud)

macroexpand-1只是扩展dummy,但不->扩展

user> (macroexpand '(dummy 1 (+ 1)))
(+ 1 1)
Run Code Online (Sandbox Code Playgroud)

macroexpand扩展dummy然后扩展->