Levelling-up my macro-fu

Hello Clojureversians,

I already wrote some macros and understand the general principle behind them (code is data and macros are just functions from code to code). However, I often stumble into Clojure code with macros techniques I’ve never understood : for example '~'form, ~'~@(some code), '~form etc. Really looks cryptic to me.

Has anyone a good resource to definetely understand macros, including these kinds of “tricks” ? Possibliy with lots of examples covering all cases.

Note : I’ve just re-read the 2 macro chapters from Clojure for the Brave and True, even this is not clear enough for my stupid brain

Thanks in advance !

I don’t immediately have a good general resource on macros, but I can tell you a bit more about these quote/unquote tricks.

The backtick (a.k.a. backquote a.k.a syntax quote) (`) that’s often used in macros is different in a few ways from the regular quote (’)

  • it allows you to “unquote” ~ or "unquote splice ~@
  • it generates unique symbols for “hygenic” macros
  • it automatically namespaces symbols

The last two things you can see in this example (I did this in lumo, hence the cljs namespaces)

`(let [foo# (bar)]) 
;;=> 
(cljs.core/let [foo__3__auto__ (cljs.user/bar)])

So foo# changed to foo_3_auto__ so it won’t clash with other names, let was resolved to a referred var in cljs.core, and so it gets turned into the fully qualified cljs.core/let, whereas bar wasn’t actually defined, so it falls back to the current namespace, cljs.user.

This namespacing is super useful, as it means you can simply use any locally referred function name in your macro, and it’ll become fully qualified so it still works when used in another namespace, but sometimes this gets in the way. To prevent Clojure from doing this the trick is to use a regular quote to quote the symbol (since this way it doesn’t get namespaced), then “unquote” that into the syntax quote form.

`(defn ~'my-fn [])
;;=>
(cljs.core/defn my-fn [])

;; compare this with the default behavior, which would be wrong in this case
`(defn my-fn [])
;; =>
(cljs.core/defn cljs.user/my-fn [])

The rest follows from there, if you want form to be a quoted symbol without namespace in the result, you use '~'form. If form is a var, and you want the value of that var to be quoted in the result, you use '~form.

Syntax quotes can be nested. This leads to some very hard to read code, but I believe that’s where your example of ~'~@(some code) comes from. Compare:

cljs.user=> `~'~@(list (symbol "foo"))
(clojure.core/unquote-splicing (list (symbol "foo")))
cljs.user=> ``~'~@(list (symbol "foo"))
(quote foo)

I think the best advice, either when writing your own macros or when reading other people’s is to make good use of the REPL. Try the syntax quote outside of the macro to see what it expands to, then when you have your macro, run macroexpand-1 and/or macroexpand on it and see if the expansion makes sense.

3 Likes

Awesome answer. The explanation for the ~' thing is something I’ve never found before. Could you elaborate further and explain the last example (the hard to read one), with use-cases etc. as you did before ? I think I need clear words and sentences to understand, experimenting alone didn’t lead me very far.

To be honest I really can’t come up with a practical use for it, I’m not even sure I understand 100% what `~'~@(some-code) is doing. in most cases it’s probably possible (and preferable) to avoid using nested syntax quotes.

If you have a piece of code you saw somewhere that has this I can help you figure it out, otherwise I wouldn’t worry about it too much.

One last “trick” that I think is cool is that if you want to have a symbol namespaced to the current namespace, you can do `foo, it’s like doing ::foo but then for symbols. I think I saw this used in one of the Juxt libraries, can’t remember which one though.

Thank you for your time and your anwers.

We just spent 1 hour at work at our weekly dev workshop studying macros : one hour of macros REPL exploration. We learned a lot ! I think I have a much better understanding of macros now, like (non-)hygienic macros, gensym (we kinda found a bug in it!), and practical use of ~'.

Day of macros !

1 Like

Be careful with macros - sometimes they are great and will save the day, some other times they will save one day and waste a week down the line :slight_smile:

I found Mastering Clojure Macros (a pretty short book) helpful. Didn’t stop me from being puzzled when trying to write a few macros. Haven’t spent as much time trying to read other people’s macros.

Don’t worry, I don’t intend to write any, just trying to understand those I encounter :wink:

Double thank you, I’m gonna buy it right now ! Exactly what I needed (I hope so at least)