Messing with Macros at the REPL
In bootstrapped ClojureScript we have macros, but they need to be defined separately from runtime code. They are typically defined in a file and loaded using
require-macros or via a
:require-macros spec in a namespace form.
But, what if you just want to mess around with macros at the ClojureScript REPL, like you can with Clojure?
It turns out that with bootstrapped ClojureScript, this is possible. You just need to know one weird trick that may give some deeper insight into how macros really work.
First, it is helpful to understand that macros are really just special functions that are called at compile time. (A plug for Colin Jones’s awesome book Mastering Clojure Macros: he covers this topic in a sidebar on page 30.)
Try defining a macro at a ClojureScript REPL:
(defmacro hello [x] `(inc ~x))
If you now type
_AMPERSAND_env arguments associated with the
&env special variables.
You can actually call this function (here we are passing
cljs.user=> (hello nil nil 13) (cljs.core/inc 13)
It produces the code that the macro would generate. But, alas, this is treated as a regular function call, not a macro call.
If you look at the meta for this function via
(meta #'hello), you will see that it has
:macro set to
true. But, what you really want is for this function to be treated as the macro it truly is.
The trick, at least in bootstrapped ClojureScript, is to define this function in a macro namespace by employing the internal compiler suffix
This, of course, depends on implementation details of the ClojureScript compiler, and you certainly shouldn’t use this for production code. But, we are really just doing this for fun and to learn.
The following works in bootstrapped ClojureScript REPLs. I’ve tried it in:
hello as a macro in the
foo.core macro namespace. First let’s employ our trick and make the macro namespace:
Now you can define macros in this namespace right at the REPL. Let’s define
hello, but also additionally print out
&form, just for fun:
(defmacro hello [x] (prn &form) `(inc ~x))
Now to call this as a macro, simply refer to its symbol in the non-macro
(foo.core/hello (+ 2 3))
And it works!
(foo.core/hello (+ 2 3)) 6
This is cool for two reasons, IMHO:
- It gives you a little insight into how macros work. (They are truly just functions that are given special treatment at compile time.)
- This trick provides a way to quickly iterate with ideas about macros at a REPL, especially when you can’t easily edit source (say, when using ClojureScript.net or Replete).