In my answer to a Code Review.SE question, I suggested that the OP might consider using records to represent chess pieces. Since the piece records would all be the same, except for the name, I figured I could generate them programmatically, like this:
(map #(defrecord % [color])
["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])
That sort of worked, but my record names weren't the piece names; they were random gensyms: instead of user.Rook
I got user.p1__910
. If I did (p1__910. :black)
, it did work and create a record, but you can probably see why I wasn't satisfied with that.
I also tried the following two variations:
(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)
What's wrong with my approach? How can I generate a bunch of identical records from a list of names? Is this even possible?
This is a classic case of macro-contagion.
user> defrecord
CompilerException java.lang.RuntimeException: Can't take value of a macro: #'clojure.core/defrecord, compiling:(/tmp/form-init802461651064926183.clj:1:5990)
You where very close with the (symbol %)
idea you just needed to make it so the defrecord expressions generated are evaluated after you provide the values.
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
If all the records are the same, why give them different names? I'd suggest:
(defrecord Chess-Piece [name color])
What's wrong with your approach is that defrecord
is a macro, so the "name" argument is interpreted as a symbol and so determines the name for the record before compiling. The mapping only occurs during runtime, after compilation.
The %
in your anonymous function is rewritten as a gensym (p1__910
), which in turn is interpreted as the symbol naming your new record.
What you want to do would have to be done with a macro -- you must simply ensure that by the time (defrecord some-symbol [color])
is evaluated (again, this is pre-runtime), some-symbol
is what you want it to be. Maybe something along the lines of:
(defmacro defpieces [names]
(let [defs (map #(list 'defrecord (symbol %) '[color])
names)]
`(do ~@defs)))
How your code is being rewritten:
(map #(defrecord % [color])
["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])
With reader macros, this turns into (roughly):
(map (fn* [p1__910#] (defrecord p1__910# [color])
["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])
defrecord
is itself a macro, so (again, before runtime) this is transformed into a giant block of code that contains:
(deftype* p1__910# user.p1__910# .....
To see the whole block, use the very useful macroexpand:
(macroexpand '(defrecord p1__910# [color]))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With