如何以编程方式生成记录定义?

时间:2015-04-20 22:02:45

标签: clojure metaprogramming

在我对a Code Review.SE question的回答中,我建议OP可能会考虑使用记录来表示棋子。由于片段记录都是相同的,除了名称,我想我可以通过编程生成它们,如下所示:

(map #(defrecord % [color]) 
      ["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])

那种方式有效,但我的唱片名称不是片名;他们是随机的gensyms:而不是user.Rook我得到user.p1__910。如果我做(p1__910. :black),它确实有效并创建了一条记录,但你可能会明白为什么我对此并不满意。

我还尝试了以下两种变体:

(map #(defrecord % [color]) 
      ['Rook 'Pawn 'Queen 'King 'Knight 'Bishop])
  ;; Same result as above.
(map #(defrecord (symbol %) [color])
           ["Rook" "Knight" "Pawn" "Queen" "King" "Bishop"])
  ;; CompilerException java.lang.ClassCastException: clojure.lang.PersistentList 
  ;; cannot be cast to clojure.lang.Symbol, compiling:(NO_SOURCE_PATH:1:7) 

我的方法有什么问题?如何从名单列表中生成一堆相同的记录?这甚至可能吗?

2 个答案:

答案 0 :(得分:5)

这是宏观传染的经典案例。

user> defrecord
CompilerException java.lang.RuntimeException: Can't take value of a macro: #'clojure.core/defrecord, compiling:(/tmp/form-init802461651064926183.clj:1:5990) 

你最接近于你需要的(symbol %)想法,以便在你提供值后评估生成的defrecord表达式。

user> (defmacro make-pieces [piece-names]
        `(do ~@(map #(list 'defrecord (symbol %) '[color]) 
                    piece-names)))
#'user/make-pieces

user> (macroexpand-1 '(make-pieces ["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"]))
(do (defrecord Rook [color]) 
    (defrecord Pawn [color]) 
    (defrecord Queen [color]) 
    (defrecord King [color]) 
    (defrecord Knight [color]) 
    (defrecord Bishop [color]))

user> (make-pieces ["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])
user.Bishop

答案 1 :(得分:5)

如果所有记录都相同,为什么要给他们不同的名字?我建议:

(defrecord Chess-Piece [name color])

您的方法出了什么问题,defrecord是一个宏,因此“name”参数被解释为符号,因此在编译之前确定记录的名称。映射仅在运行时发生,在编译后

匿名函数中的%被重写为gensym(p1__910),后者又被解释为命名新记录的符号。

你想要做的事情必须用宏来完成 - 你必须简单地确保在评估时(defrecord some-symbol [color])(再次,这是预运行时),some-symbol是什么你想要它。也许有些东西:

(defmacro defpieces [names]
  (let [defs (map #(list 'defrecord (symbol %) '[color])
                  names)]
    `(do ~@defs)))

您的代码如何被重写:

(map #(defrecord % [color]) 
  ["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])

对于读者宏,这变成(粗略地):

(map (fn* [p1__910#] (defrecord p1__910# [color])
  ["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])

defrecord本身就是一个宏,所以(再次,在运行时之前),它会被转换为包含以下内容的巨大代码块:

(deftype* p1__910# user.p1__910# .....

要查看整个块,请使用非常有用的macroexpand

(macroexpand '(defrecord p1__910# [color]))