2015-04-20 47 views
4

在我对a Code Review.SE question的回答中,我建议OP可以考虑使用记录来表示棋子。由于片记录都将是一样的,除了名字,我想我可以以编程方式生成它们,就像这样:如何以编程方式生成记录定义?

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

之类的工作,但我的记录名称不是那块名称;他们是随机gensyms:而不是user.Rook我得到。如果我做了(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) 

这有什么错我的做法?我如何从一个名称列表中生成一堆相同的记录?这甚至有可能吗?

回答

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 [email protected](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 
+0

我与宏无能又咬我。谢谢你的回答。 – tsleyson

5

如果所有记录都一样,为什么给它们不同的名字?我建议:

(defrecord Chess-Piece [name color]) 

有什么问题你的做法是,defrecord是一个宏,因此,“名”参数被解释为一个符号,所以决定了名称备案编译之前。映射仅在运行时发生,编译后

您匿名函数中的%被重写为一个gensym(p1__910),它反过来被解释为命名您的新记录的符号。

你想要做的事情必须用宏来完成 - 你必须简单地确保到(defrecord some-symbol [color])被评估(再次,这是运行前),some-symbol是你想要的。也许沿着线的东西:

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

如何使你的代码被改写:

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

如果您只有一个棋子记录,则可以使用实体组件范例来实现移动;每件作品都是一个实体,每一种移动方式都是一个组成部分。 – galdre

+0

很好的解释,谢谢。对我来说,使用单独的记录来允许单独实现一个协议,比如限制国王可以移动的空间数量,这让我感觉更加清洁。但我对游戏编程一无所知。如果你有另外一种方法,我敢打赌,[代码评论问题](http://codereview.stackexchange.com/q/87325/52814)的启发让我想问这个问题,希望听到它。 – tsleyson