views:

130

answers:

1

I'm trying (as a self-learning exercise) to create a Clojure macro that will generate code to apply a function to a sequence of integers and sum the result, e.g.

f(0) + f(1) + f(2) + f(3)

This is my attempt:

(defmacro testsum [func n] 
  `(fn [x#] (+ ~@( map (fn [i] `(~func x#)) (range n)))))

However something seems to go wrong with the x# gensym and I end up with two different versions of x and hence the function doesn't work:

(macroexpand '(testsum inc 3))

gives:

(fn* ([x__809__auto__] 
  (clojure.core/+ 
    (inc x__808__auto__) 
    (inc x__808__auto__) 
    (inc x__808__auto__))))

This is pretty much exactly what I want apart from the different 809 and 808 versions of x.....

What am I doing wrong? I thought that the auto gensym was meant to create a single unique symbol for exactly this kind of purpose? Is there a better way of doing this?

+7  A: 

foo#-style gensyms are valid only inside the syntax-quote where they were created. In your code, the two x#s are created in different syntax-quote blocks:

(defmacro testsum [func n] 
  `(fn [x#] (+ ~@( map (fn [i] `(~func x#)) (range n)))))
  ^- s-q1      ^-unquote       ^- s-q2

To fix this, use an explicit (gensym) call:

(defmacro testsum [func n]
  (let [x (gensym "x")]
    `(fn [~x] (+ ~@(map (fn [i] `(~func ~x)) (range n))))))

And the macro expansion ((macroexpand '(testsum inc 3))):

(fn* ([x4966] (clojure.core/+ (inc x4966) (inc x4966) (inc x4966))))
Michał Marczyk
As an afterthought, it's also possible to replace `(gensym "x")` with `\`x#`, though I've never seen anyone do that for a gensym created explicitly outside of the main expansion-generating form.
Michał Marczyk
Awesome Michal - works perfectly many thanks! Still getting my head around symbols after coming from a Java world....
mikera