【发布时间】:2016-08-31 02:21:00
【问题描述】:
我不明白macroexpand 和macroexpand-1 之间的区别。
你能提供例子吗?
【问题讨论】:
标签: clojure functional-programming macros lisp
我不明白macroexpand 和macroexpand-1 之间的区别。
你能提供例子吗?
【问题讨论】:
标签: clojure functional-programming macros lisp
假设我们有以下代码:
(defmacro inner-macro [arg]
`(println ~arg))
(defmacro top-level-macro [arg]
`(inner-macro ~arg))
(defn not-a-macro [] nil)
然后,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)
换句话说,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-1 和 macroexpand 只需返回它即可。不要指望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 然后扩展 ->
【讨论】: