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

tsl*_*son 4 metaprogramming clojure

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

(map #(defrecord % [color]) 
      ["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])
Run Code Online (Sandbox Code Playgroud)

那种方式有用,但我的唱片名不是片名; 他们是随机的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) 
Run Code Online (Sandbox Code Playgroud)

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

Art*_*ldt 5

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

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

(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
Run Code Online (Sandbox Code Playgroud)


gal*_*dre 5

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

(defrecord Chess-Piece [name color])
Run Code Online (Sandbox Code Playgroud)

你的方法有什么问题,那defrecord就是一个宏,所以"name"参数被解释为一个符号,因此在编译之前确定记录的名称.映射仅在运行时,编译后发生.

%您的匿名函数被改写为gensym( p1__910),这反过来又被解释为符号命名你的新纪录.

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

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

您的代码如何被重写:

(map #(defrecord % [color]) 
  ["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])
Run Code Online (Sandbox Code Playgroud)

使用阅读器宏,这变成(大致):

(map (fn* [p1__910#] (defrecord p1__910# [color])
  ["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])
Run Code Online (Sandbox Code Playgroud)

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

(deftype* p1__910# user.p1__910# .....
Run Code Online (Sandbox Code Playgroud)

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

(macroexpand '(defrecord p1__910# [color]))
Run Code Online (Sandbox Code Playgroud)