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

How can I programmatically generate record definitions?

在我对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) 

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

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

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

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

(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"])

使用 reader 宏,这变成(大致):

(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]))