Messing with Macros at the REPL

September 7, 2015

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 hello at the REPL, you will see the JavaScript function emitted for the macro. In it, you'll also see the _AMPERSAND_form and _AMPERSAND_env arguments associated with the &form and &env special variables.

Update June 16, 2017: With CLJS-2015, defmacro now returns the macro Var, #'cljs.user/hello, so to see the JavaScript code associated with the macro, evaluate hello.

You can actually call this function (here we are passing nil for &form and &env):

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 $macros.

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:

Let’s define hello as a macro in the foo.core macro namespace. First let’s employ our trick and make the macro namespace:

(ns foo.core$macros)

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 namespace:

(foo.core/hello (+ 2 3))

And it works!

(foo.core/hello (+ 2 3))
6

This is cool for two reasons, IMHO:

  1. It gives you a little insight into how macros work. (They are truly just functions that are given special treatment at compile time.)
  2. 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).

Have fun!

Tags: ClojureScript Bootstrap