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.