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



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

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

这种方法奏效了,但我的唱片名并不是作品名;它们是随机的同义词:我得到的不是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"])

对于阅读器宏,这(大致)变成:

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

defrecord本身就是一个宏,因此(同样,在运行时之前)它被转换为一个巨大的代码块,其中包含:

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

要查看整个块,请使用非常有用的宏展开:

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

最新更新