どのようにすればプログラムでレコードを定義できる?
これ、答えはすっと出て朝から回答しようと思ったけど、電車に乗ってる間に先を越されてしょぼんしています。
ただ、僕よりずっといい答えが書かれているのでさくっと書いてみます。
この質問はその前にコードレビューの方でチェスの問題に対する回答をした人が「チェスの駒って名前が違うだけだから、名前の一覧だけあれば全部プログラムで駒を定義出来ると思うんだけど…」というところからスタートします。
まず質問した人の答えはこう。
(map #(defrecord % [color]) ["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])
なんとなく良さそうに見えます。ですが、これは動きません。僕の環境で実行したらこういう結果が出ます。
(user.p1__15152# user.p1__15152# user.p1__15152# user.p1__15152# user.p1__15152# user.p1__15152#)
期待どおりに動いてなさそうです。期待値は user.Rook user.Pawn ... です。
こうなるならマクロ使えば良さそうなのはすぐに思いつきます。
(defmacro defpieces [names] (let [defs (map #(list 'defrecord (symbol %) '[color]) names)] `(do ~@defs)))
ただ、回答者の galdre 曰く「そもそも、なんでレコードに全部違う名前つける必要あるの?」らしく「僕はこっちをオススメするけどね」と別解を示しています。
(defrecord Chess-Piece [name color])
確かに一理あるなーと。 Clojure ではマクロはあまり推奨されていないと僕は聞いているし、この方法は確かにマクロ使う必要もなく元の問題を解決できそうだなーという気がします*1。
さらに galdre はちゃんとなんで元の質問の状況になるかも説明しています。
(map #(defrecord % [color]) ["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])
これはリーダーマクロを使っているため、読み込み時におおよそ次のように展開されていると説明しています。
(map (fn* [p1__910#] (defrecord p1__910# [color]) ["Rook" "Pawn" "Queen" "King" "Knight" "Bishop"])
さらに defrecord そのものもマクロであるために実行前に展開されます。なので p1_910# というシンボルがそのままレコードの名前として使用されてしまうわけですね。
ちなみに
僕ならこう書きたいなって思いました。
(defmacro defpiece [name] `(defrecord ~(symbol name) ~'[color])) (defmacro defpieces [& names] (let [pieces (map #(@#'defpiece nil nil %) names)] `(do ~@pieces))) (defpiece "Rook") (defpieces "Rook" "Pawn" "Queen" "King" "Knight" "Bishop")