I am leaning macros in Clojure and have a question about macro expansion. In the repl, when I do this:
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)
But when I do the same in a clj file:
(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))))
and run the code, I get this:
$ lein run
(unless (> 5 3) :foo :bar)
How do I get the code to print the same as the repl?
This happens because of how the notion of the current namespace works in Clojure. macroexpand-1
expands its argument in the current namespace.
At the REPL, this will be user
; you're defining the macro in the user
namespace, then you call macroexpand-1
in that namespace and all is good.
In a :gen-class'd
namespace, or indeed any other namespace, the compilation-time current namespace is that namespace itself. When you later call the code defined in this namespace, however, the then-current namespace will be whatever is appropriate at that point. That might be some other namespace as it gets compiled.
Finally, at your app's runtime, the default current namespace is user
.
To see this, you could move the macro to a separate namespace also defining a function use-the-macro
and calling this function at top level; the :gen-class
'd namespace would then need to require or use the macro's namespace. Then lein run
will print what you expect once (at the macro's namespace's compilation time) and the unexpanded form twice (when the macro's namespace is require
d by the main namespace and then when -main
calls use-the-macro
).
The Clojure REPL controls the current namespace using binding
; you can do the same:
(binding [*ns* (the-ns 'scratchpad.core)]
(prn (macroexpand-1 ...)))
You can also use syntax-quote instead of quote in -main
:
(defn -main [& args]
(prn (macroexpand-1 `...)))
^- changed this
Of course if symbols other than unless
were involved, you'd have to decide whether they should be namespace-qualified in the output and possibly prefix them with ~'
. This is the point though -- syntax-quote is good for producing mostly "namespace-independent" code (which is what makes it so great for writing macros, besides the convenient syntax).
Another possible "fix" (tested on Clojure 1.5.1) is adding an in-ns
call to -main
:
(defn -main [& args]
(in-ns 'scratchpad.core)
(prn (macroexpand-1 '...)))
^- no change here this time
As with binding
, this way you're actually getting the expansion of your original form in your original namespace.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With