简体   繁体   中英

Coordinating auto-gensym in nested syntax-quotes in Clojure

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?

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.

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)))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM