Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid anaphoric macro in Clojure?

Tags:

macros

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?

like image 200
Philip Kamenarsky Avatar asked Mar 19 '12 02:03

Philip Kamenarsky


2 Answers

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.

like image 95
amalloy Avatar answered Sep 30 '22 00:09

amalloy


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]
like image 44
mikera Avatar answered Sep 30 '22 00:09

mikera