Dav*_*ams 5 macros clojure read-eval-print-loop
我在Clojure中使用宏,并对宏扩展有疑问.在repl中,当我这样做时:
user=> (defmacro unless [pred a b] `(if (not ~pred) ~a ~b))
#'user/unless
user=> (macroexpand-1 '(unless (> 5 3) :foo :bar))
(if (clojure.core/not (> 5 3)) :foo :bar)
Run Code Online (Sandbox Code Playgroud)
但是当我在clj文件中执行相同操作时:
(ns scratch-pad.core
(:gen-class))
(defmacro unless [pred a b]
`(if (not ~pred) ~a ~b))
(defn -main [& args]
(prn
(macroexpand-1 '(unless (> 5 3) :foo :bar))))
Run Code Online (Sandbox Code Playgroud)
并运行代码,我得到这个:
$ lein run
(unless (> 5 3) :foo :bar)
Run Code Online (Sandbox Code Playgroud)
如何让代码与repl一样打印?
这是因为当前命名空间的概念在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 ...)))
Run Code Online (Sandbox Code Playgroud)
您还可以使用syntax-quote而不是引用-main:
(defn -main [& args]
(prn (macroexpand-1 `...)))
^- changed this
Run Code Online (Sandbox Code Playgroud)
当然,如果unless涉及的符号之外,您必须决定它们是否应该在输出中进行名称空间限定,并且可能为它们添加前缀~'.这是重点 - 语法引用有利于生成大多数"与命名空间无关"的代码(除了方便的语法之外,这使得它非常适合编写宏).
另一个可能的"修复"(在Clojure 1.5.1上测试)正在添加一个in-ns调用-main:
(defn -main [& args]
(in-ns 'scratchpad.core)
(prn (macroexpand-1 '...)))
^- no change here this time
Run Code Online (Sandbox Code Playgroud)
就像binding这样,您实际上是在原始命名空间中扩展原始表单.