好的,我想编写一个定义struct-map的Clojure宏,让调用者为每个字段指定类型。
签名将如下所示:
(defmodel category :id Integer :name String)
这样做会创建一个名为struct-map
的类别,并创建一个绑定*category-meta*
,即地图{:id Integer :name String}
这是我实现这个目标的宏:
(defmacro defmodel [name & field-spec]
`(let [fields# (take-nth 2 ~@field-spec)]
(defstruct ~name fields#)
(def *~name-meta* (reduce #(assoc %1 (first %2) (last %2))) (partition 2 ~@field-spec))))
然而,问题是,我无法定义名称由另一个名称组成的绑定。基本上,(def *~name-meta* ...)
不起作用。
我如何实现这一目标?
感谢。
答案 0 :(得分:4)
(使用问题文本中的调试版宏更新。)
这应该按照规定运作:
(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)))))
主要问题的答案是使用~(symbol (str "*" name "-meta*"))
代替*~name-meta*
。 ~
以语法引用的形式取消引用下一个表达式,将其返回值注入给定列表结构的适当位置。
其他一些修改是必要的 - 特别是,defstruct
要求将密钥作为单独的参数提供给它,而不是单个seq(或者包含这样的seq的变量的名称),{{ 1}}需要显式种子值才能在这里工作等。
顺便说一下,除非你需要坚持使用Clojure 1.1,否则你可能希望使用1.2 reduce
优先于defrecord
- 事实上,后者在1.2中已被弃用。