2

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__)
User0123456789
  • 760
  • 2
  • 10
  • 25
Xiufen Xu
  • 531
  • 1
  • 3
  • 19

1 Answers1

4

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#]))
coredump
  • 37,664
  • 5
  • 43
  • 77
  • okay, now I know that for this problem we don't need to use gensym. But here this another problem, I think we must use gensym: Write a macro (for-loop [symb init test change] & body]) to compute a for loop as in the C language by specifying a variable, its initial value, the exit condition, and the body of the loop. It should work like this: `(for-loop [i 0 , (< i 4) , (inc i)] (println i))` and print 0,1,2,3,nil. – Xiufen Xu Apr 04 '16 at 16:19
  • And my code is `(defmacro for-loop [[symb ini t change] & body]` ` `(loop [symb# ~ini] (if ~t ~@body (recur ~change))))` When I use macroexpand to check the code: `user=> (macroexpand '(for-loop [i 0 (< i 4) (inc i)] (println i))) (loop* [symb__4615__auto__ 0] (if (< i 4) (println i) (recur (inc i))))`. So how to sovle this problem? Thanks ! – Xiufen Xu Apr 04 '16 at 16:24
  • 2
    @X iufenXu Please update the question instead, with a code block. That will be easier to read. I'll have a look at this too. – coredump Apr 04 '16 at 16:26
  • Thank you so much for your explanations! You really clear up my confusion. However, I am still confused about the last example you took. I mean I still have no idea when should I use gensym. – Xiufen Xu Apr 04 '16 at 17:05
  • Thanks. If I made a vector with two "copies" of `expr`, the supplied expression would have been evaluated twice. Here, I want it to be evaluated once (it might be important if you have side-effects or if the computation takes time) and make a list with the resulting *value* twice (it is a little silly example). In order to do that, I need to have an intermediate variable to hold the result. In general, in those cases, there should be a new symbol. To avoid conflicts, fresh symbols are required. – coredump Apr 04 '16 at 17:19
  • Got you! Really appreciate your help! – Xiufen Xu Apr 04 '16 at 17:28