Clojure的宏 - 定义一个绑定,其名称由一个参数组成

时间:2010-11-26 20:26:57

标签: macros clojure metaprogramming

好的,我想编写一个定义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* ...)不起作用。

我如何实现这一目标?

感谢。

1 个答案:

答案 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中已被弃用。