Reputation: 2701
I'm wrapping defrecord
with a defmacro
, because I want a specific type of concrete map to register itself with a central place (for serde).
The macro is:
(defmacro def-thing! [name [& fields] & opts+specs]
`(let [klass# (defrecord ~name ~fields ~@opts+specs)
map-constructor-fn# (resolve (symbol (str 'map-> ~name)))]
; use the map-constructor-fn#
))
The problem is that the let
form gets macroexpanded differently based on whether the macro has already been called before (like when I reload the namespace source file in nrepl).
The first time, name
is a Symbol.
The second time, name
is a Java class.
I can, of course, test to see if name~
is a Java class, and then call .getSimpleName()
and do the appropriate thing.
But the core contributors to Clojure must have run into this? How is this handled? And why don't I see an explicit solution in the Clojure core source for defrecord
or emit-defrecord
? What am I missing?
Upvotes: 2
Views: 75
Reputation: 2701
Well, I'm an idiot. I found this in "Joy of Clojure":
(defmacro def-thing! [name [& fields] & opts+specs]
`(let [klass# (defrecord ~name ~fields ~@opts+specs)
map-constructor-fn# (resolve (symbol (str 'map-> '~name)))]
; use the map-constructor-fn#
))
Notice the (quote (unquote <symbol>))
- i.e. '~<symbol>
- form. That idiom forces capture of the raw, unqualified symbol passed in.
The curious thing is that I'm not passing that to the defrecord
call. Internally, defrecord
does the same capture of raw symbol, except through a var
call, presumably because defrecord
, when called the first time, would assign the symbol to a fully qualified class var. In fact, '~name
specifically does not work with the defrecord
call, and I get a clojure.lang.Cons cannot be cast to clojure.lang.Named
exception.
This seems like inconsistency to me, but what do I know?
Upvotes: 1