Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(gensym) is always unique, `(symb#) is not -- why?

Tags:

clojure

If I write a macro that uses the symb# shortcut to create a gensym which is then bound as a global variable, the exact same symbol gets generated over and over. However, it functions correctly if I call gensym manually. Very simple examples:

(defmacro indirection
    [name & body]
    `(do (def name# ~@body)
         (defn ~name [] name#)))

(indirection foo 3)
(foo) ; ⇒ 3
(indirection goo 5)
(goo) ; ⇒ 5
(foo) ; ⇒ 5

The problem is apparent if you use macroexpand:

(macroexpand '(indirection foo 3))
(do (def name__2863__auto__ 3) (clojure.core/defn foo [] name__2863__auto__))
(macroexpand '(indirection foo 3))
(do (def name__2863__auto__ 3) (clojure.core/defn foo [] name__2863__auto__))

This problem goes away if I call gensym the long way:

(defmacro redirection
    [name & body]
    (let [rename (gensym)]
        `(do (def ~rename ~@body)
             (defn ~name [] ~rename))))

(redirection koo 3)
(koo) ; ⇒ 3
(redirection moo 5)
(moo) ; ⇒ 5
(koo) ; ⇒ 3

So, why the difference? What am I missing?

like image 707
galdre Avatar asked Apr 14 '15 19:04

galdre


1 Answers

Syntax quoting with ` is actually a reader macro; the form that follows it is transformed by the reader (which translates text to Clojure forms) prior to evaluation. This means that any symbol ending in # within the syntax-quoting is translated to an autogenerated symbol only once, when the text is first read; that autogenerated symbol is then inserted directly into the macro definition, and appears verbatim in the macroexpanion every time that macro is invoked. This can be illustrated easily at the REPL:

user=> `(foo bar#)
(user/foo bar__2288__auto__)
user=> `(foo bar#)
(user/foo bar__2291__auto__)

The typical use case for auto-gen'ed symbols with # is to define local variables inside a quoted let or fn form. There, it does not matter that the same symbol is re-used for multiple macro invocations; it only needs to be unique within each invocation. For instance:

(defmacro indirection
  [name body]
  `(let [name# ~body]
     (defn ~name [] name#)))
like image 64
Alex Avatar answered Nov 16 '22 10:11

Alex