Take this (simplified) example:
(defmacro make [v & body]
`(let [~'nv ~(some-calc v)]
~(map #(if (= % :value) 'nv %) body)))
Right now the symbol nv
is hardcoded. Is there a way to somehow gensym nv
and still being able to use it in the map function?
By the way, is this actually an anaphoric macro?
The answer is contained within the question: just use gensym
like you would if Clojure didn't have auto-gensyms.
(defmacro make [v & body]
(let [value-sym (gensym)]
`(let [~value-sym ~(some-calc v)]
~@(replace {:value value-sym} body))))
Note that I'm not sure whether you really want ~
or ~@
here - it depends if body
is supposed to be a sequence of expressions to execute in the let
, or a sequence of arguments to a single function call. But ~@
would be a lot more intuitive/normal, so that's what I'm going to guess.
Whether this macro is anaphoric is a little questionable: definitely introducing nv
into the calling scope was, but it was basically unintentional so I would say no. In my revised version, we're no longer introducing nv
or anything like it, but we are "magically" replacing :value
with v
. We only do that at the topmost level of the body, though, so it's not like introducing a real scope - I'd say it's more like making the client's code unexpectedly break in corner cases.
For an example of how this deceptive behavior can crop up, imagine that one of the elements of body
is (inc :value)
. It won't get replaced by the macro, and will expand to (inc :value)
, which never succeeds. So instead I'd recommend a real anaphoric macro, which introduces a real scope for a symbol. Something like
(defmacro make [v & body]
`(let [~'the-value ~(some-calc v)]
~@body))
And then the caller can just use the-value
in their code, and it behaves just like a real, regular local binding: your macro introduces it by magic, but it doesn't have any other special tricks.
It's not actually an anaphoric macro as I understand it.
An anaphoric equivalent would have give you a syntax like:
(make foo 1 2 3 4 it 6 7 it 8 9)
i.e. the symbol it
has been defined so that it can be used inside the body of the make macro.
I'm not sure precisely if this is what you want because I don't have enough context on how this macro is going to be used, but you could implement the above like:
(defmacro make [v & body]
`(let [~'it ~v]
(list ~@body)))
(make (* 10 10) 1 2 3 4 it 6 7 it 8 9)
=> (1 2 3 4 100 6 7 100 8 9)
Alternatively, if you're not really trying to create new syntax and just want to replace :value
in some collection, then you don't really need a macro: it would be better to just use replace:
(replace {:value (* 10 10)} [1 2 :value 3 4])
=> [1 2 100 3 4]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With