简体   繁体   中英

How to avoid anaphoric macro in Clojure?

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)

ie 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]

One approach is to use a dynamic binding.

(declare ^:dynamic *nv*)

(defmacro make [v & body]
  `(binding [*nv* ~(some-calc v)]
     ~(map #(if (= % :value) *nv* %) body)))

In practice, dynamic variables suck when their scope is too wide (it's harder to test and debug programs etc.), but in cases like this where the scope is confined to the local calling context where an anaphor is needed, they can be quite handy.

An interesting aspect of this use is that it's kind of the inverse of a common idiom of using a macro to hide a dynamic binding (many in the with-* style). In this idiom (which as far as I know is not all that common) the binding is used to expose something hidden by the macro.

In your example, both some-calc and map happen during macroexpansion, not at runtime, thus nv does not need to be let anyway. The macro itself is not written properly, irrespective of anything to do with symbol capture.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM