Search code examples
clojuregensym

how to apply gensym to each specific variable


I want to write a macro (my-dotimes [x init end] & body) that computes the value of body for x going from init to end-1 in increments of 1. Here you again have to make sure to avoid the "variable capture problem". It should work like this:

user=> (my-dotimes [x 0 4] (print x))
0123nil

my code is :

(defmacro my-dotimes [[x initial end] & body]
`(loop [i# ~initial]
    (when (< i# ~end)
        ~@body
        (recur (inc i#))))))

but when I use macroexpand to check it and find:

user=> (macroexpand '(my-dotimes [x 0 4] (println x)))
(loop* [i__4548__auto__ 0] (clojure.core/when (clojure.core/<i__4548__auto__ 4)
 (println x) 
(recur (clojure.core/inc i__4548__auto__))))

I am wondering how to change

(println x) => (clojure.core/println i__4548__auto__)

Solution

  • Here, you supply the symbol that should be bound to the counter (here x), so you don't need to use gensyms. Instead of using i#, just introduce the symbol given to you by the user of the macro. You need gensyms when you introduce new symbols and don't want them to collide with existing symbols.

    In Common Lisp, it would make sense to wrap the body with a binding from the user-supplied symbol to the current value of i, using (let ((,x ,i)) ,@body), because the user's code could change the value of the counter during iteration (which could be bad). But here I think you cannot mutate the variable directly, so you don't need to worry about that.

    Your second example is:

    (defmacro for-loop [[symb ini t change] & body]
      `(loop [symb# ~ini] 
         (if ~t 
             ~@body
             (recur ~change))))
    

    First problem: when you expand the body, which might be one or more form, you'll end-up with an if form with many branches instead of 2. You would have for example (if test x1 x2 x3 (recur ...)) if your body contains x1, x2 and x3. You need to wrap bodies in do expressions, with (do ~@body).

    Now, the situation is not very different than before: you still have a symbol, given by the user, and you are responsible for establishing the bindings in the macro. Instead of using symb#, which creates a new symbol, completely distinct from symb, just use symb directly. You could do this for example (untested):

    (defmacro for-loop [[symb init test change] &body]
      `(loop [~symb ~init]
         (if ~test (do ~@body) (recur ~change))))
    

    As long as you use the symbol provided by the caller of your macro, gensyms are not necessary. You need gensyms when you have to create a new variable in the generated code, which requires to have a fresh symbol. For example, you evaluate an expression only once and need a variable to hold its value:

    (defmacro dup [expr]
      `(let [var# ~expr]
          [var# var#]))