Coordinating auto-gensym in nested syntax-quotes in Clojure
Asked Answered
L

3

16

In Clojure, you need to use gensym to create symbols for internal use in your macros to keep them hygienic. However, sometimes you need to use the same symbol in nested syntax-quotes. For example, if I want to bind a value to a symbol with let and print it three times in an unrolled loop, I'd do

`(let [x# 1]
   ~@(repeat 3
             `(println x#)))

But that would produce

(clojure.core/let [x__2__auto__ 1]
                  (clojure.core/println x__1__auto__)
                  (clojure.core/println x__1__auto__)
                  (clojure.core/println x__1__auto__))

x# generates a different symbol in the let form than in the println forms nested within it - because they were created from different syntax-quotes.

To solve it, I can generate the symbol beforehand and inject it to the syntax-quotes:

(let [x (gensym)]
  `(let [~x 1]
     ~@(repeat 3
               `(println ~x)))
) 

This will produce the correct result, with the same symbol everywhere needed:

(clojure.core/let [G__7 1]
                  (clojure.core/println G__7)
                  (clojure.core/println G__7)
                  (clojure.core/println G__7))

Now, while it does produce the right result, the code itself looks ugly and verbose. I don't like having to "declare" a symbol, and the injection syntax makes it look like it came from outside the macro, or calculated somewhere within in it. I want to be able to use the auto-gensym syntax, which makes it clear that those are macro-internal symbols.

So, is there any way to use auto-gensym with nested syntax-quotes and make them produce the same symbol?

Librettist answered 1/10, 2012 at 15:40 Comment(0)
T
12

Auto-gensym'd symbols are only valid within the syntax-quote that defines them and they don't work in unquoted code because that is not part of the syntax quote.

Here the symbol x# gets replaced by it's gensym because it is within the scope of the syntax quote:

core> `(let [x# 1] x#)
(clojure.core/let [x__1942__auto__ 1] x__1942__auto__)

And if you unquote it it no longer gets translated into it's syntax quote:

core> `(let [x# 1] ~@x#)
CompilerException java.lang.RuntimeException: Unable to resolve symbol: x# in this context, compiling:(NO_SOURCE_PATH:1) 

Auto-gensyms are a very convenient shortcut within the syntax-quote, everywhere else you apparently need to use gensym directly as is the case with your later example.

there are other ways to structure this macro so autogensyms will work though declaring gensymed symbols in a let at the top of a macro is very normal in Clojure and other lisps as well.

Titan answered 1/10, 2012 at 16:4 Comment(3)
Thanks. I can't get normal-quoting the print expression to work, but I suppose it'll become cumbersome for more complex macros anyways...Librettist
If you do something like this, I strongly recommend using `println, not 'println - the former is not subject to name-capture in case the macro user has an unusual namespace or lexical context.Varityper
@IdanArye sorry, it was not working because my answer was wrong :-( I added a hopefully correct editTitan
C
9

Your method (calling gensym) is the right one.

However in some cases you can get by with a clever use of doto, -> or ->>. See:

 `(let [x# 1]
   (doto x#
     ~@(repeat 3 `println)))
Cockadoodledoo answered 2/10, 2012 at 9:36 Comment(1)
I actually thought about using doto, but failed to find the command - I was looking for something along the lines of dowith, because that's what I'm used to from other languages...Librettist
B
1

More generally you can do the following whenever faced with this situation:

(let [x `x#]
   `(let [~x 1]
      ~@(repeat 3
                `(println ~x))))

To be clear, you create the auto-gensym and bind it outside of the syntax-quoted form, and then inside any nested forms which require it you can just use syntax-unquote.

Backdate answered 20/6, 2021 at 5:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.