开发者

Clojure's macro - define a binding whose name is composed from an argument

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜