发生了什么
这是因为在Clojure中,当前命名空间的概念如何工作而导致的。 macroexpand-1
在当前命名空间中扩展其参数。
在REPL中,这将是user
; 您正在定义宏在user
命名空间中,然后在该命名空间中调用macroexpand-1
,一切正常。
在:gen-class'd
命名空间或任何其他命名空间中,编译时当前命名空间就是该命名空间本身。 但是,当您稍后调用在此命名空间中定义的代码时,当前命名空间将是在那个时间点上适当的任何其他命名空间。 随着编译过程的进行,可能会出现一些其他的命名空间。
最后,在应用程序的运行时,默认的当前命名空间是user
。
要查看这一点,您可以将宏移动到单独的命名空间,该命名空间还定义一个use-the-macro
函数并在顶层调用此函数; 然后,:gen-class
'd命名空间需要要求或使用宏的命名空间。 然后,lein run
将在一次打印宏的命名空间编译时打印您期望的内容,并在主命名空间require
宏的命名空间以及-main
调用use-the-macro
时打印未展开的形式两次。
解决方案
Clojure REPL使用binding
控制当前命名空间; 您也可以这样做:
(binding [*ns* (the-ns 'scratchpad.core)]
(prn (macroexpand-1 ...)))
在-main
中,您也可以使用语法引用(syntax-quote)而不是引用(quote):
(defn -main [& args]
(prn (macroexpand-1 `...)))
^- changed this
当然,如果除了“unless”之外的符号也涉及到,您需要决定它们是否应该在输出中加上命名空间限定,并可能用“~'”作为前缀。这就是关键所在——语法引用适用于生成大多数“与命名空间无关”的代码(这也是编写宏非常好的原因之一,除了方便的语法)。
另一个可能的“修复”方法(在Clojure 1.5.1上测试)是在“-main”中添加一个“in-ns”调用:
(defn -main [& args]
(in-ns 'scratchpad.core)
(prn (macroexpand-1 '...)))
^- no change here this time
与
绑定(binding)
类似,这种方式实际上是在您的原始命名空间中获取原始表单的扩展。