OK, I want to write a Clojure macro that defines a struct-map and let caller to specify types for each field.
The signature will look like this:
(defmodel category :id Integer :name String)
What this does is it creates a struct-map
called category, and also create a binding *category-meta*
, which is a map {:id Integer :name String}
Here's my macro to achieve this:
(defmacro defmodel [name & field-spec]
`(let [fields# (take-nth 2 ~@field-spec)]
(defstruct ~name fields#)
(def *~name-meta* (开发者_StackOverflow中文版reduce #(assoc %1 (first %2) (last %2))) (partition 2 ~@field-spec))))
However, the problem is, I cannot define a binding whose name is composed of another name. Basically, (def *~name-meta* ...)
doesn't work.
How am I able to achieve this?
Thanks.
(Updated with a debugged version of the macro from the question text.)
This should work as specified:
(defmacro defmodel [name & field-spec]
`(do (defstruct ~name ~@(take-nth 2 field-spec))
(def ~(symbol (str "*" name "-meta*"))
(reduce #(assoc %1 (first %2) (last %2))
{}
(partition 2 '~field-spec)))))
The answer to the main question is to use ~(symbol (str "*" name "-meta*"))
in place of *~name-meta*
. ~
unquotes the next expression in a syntax-quoted form, injecting its return value in the appropriate spot of the given list structure.
Some other modifications were necessary -- in particular, defstruct
requires that the keys be supplied to it as separate arguments, rather than a single seq (or the name of a variable holding such a seq), reduce
needs the explicit seed value to work here etc.
Incidentally, unless you need to stick to Clojure 1.1, you might want to use 1.2's defrecord
in preference to defstruct
-- in fact, the latter is deprecated in 1.2.
精彩评论