Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure How to Expand Macro in Source File Versus Repl

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?

like image 863
David Williams Avatar asked May 12 '13 21:05

David Williams


1 Answers

What's going on

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 required by the main namespace and then when -main calls use-the-macro).

Solutions

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.

like image 133
Michał Marczyk Avatar answered Sep 28 '22 02:09

Michał Marczyk