Why this Clojure "var" error?

We were attempting to write functions could round-trip function references to strings and, though we eventually figured this out in a different way, our first attempts brought a very confusing error. It seems to have something to do with how var works, and some underlying characteristic of Clojure that I would love to understand.

We were adapting this macro, which worked in the REPL: (defmacro fn-name [f] `(-> ~f var meta :name str)) . But it failed when written as a larger fn, below, and even with macro-expand1 it wasn’t clear what was going wrong, although trial and error indicated that it has something do with with how var works. Can anyone shed some light on the nature of the problem?

(defn foo [] nil)

(defmacro parse-fn->string_fails
  "Should parse a function, like `clojure.core/str`, to a string \"clojure.core/str\" "
  [f]
  `(let [m (-> ~f var meta)
	 fname (:name m)
	 fns (:ns m)]
     (str fns "/" fname)))

(parse-fn->string_fails foo)
;; =>
;; Call to clojure.core/let did not conform to spec.
;;    #:clojure.spec.alpha{:problems ({:path [:bindings :form :local-symbol], :pred clojure.core/simple-symbol?, :val logging.core/m, :via [:clojure.core.specs.alpha/bindings :clojure.core.specs.alpha/bindings ...

Local symbols in macros must be gensym’d, i.e., m#, fname#, fns#.

Per sean’s response:

user>(pprint (macroexpand-1 '(parse-fn->string_fails foo)))

(clojure.core/let
 [user/m
  (clojure.core/-> foo var clojure.core/meta)
  user/fname
  (:name user/m)
  user/fns
  (:ns user/m)]
 (clojure.core/str userfns "/" user/fname))

(defmacro parse-fn->string_fails
  "Should parse a function, like `clojure.core/str`, to a string \"clojure.core/str\" "
  [f]
  `(let [m# (-> ~f var meta)
         fname# (:name m#)
         fns# (:ns m#)]
     (str fns# "/" fname#)))

now yields:

(clojure.core/let
    [m__16497__auto__
     (clojure.core/-> foo var clojure.core/meta)
     fname__16498__auto__
     (:name m__16497__auto__)
     fns__16499__auto__
     (:ns m__16497__auto__)]
  (clojure.core/str fns__16499__auto__ "/" fname__16498__auto__))

Which works. In the fn-name macro, you didn’t have this problem since you didn’t have a let binding with symbols. In the new macro, without gensyms, the unquoted symbols in the let were expanded into ns-qualified symbols, which were unable to resolve and triggered errors during evaluation of the let form. With the gensyms generating valid unqualified symbols for let, you now have valid bindings and the macro works.

2 Likes

Ah! That makes perfect sense! Thank you!