最初に
個人的にcommon-lispにある多くのデータ構造の中でリストというものを
よく使っている。ところが、構造体となると少し構えるし、
ちょっと扱いが面倒臭いなと感じる事もある。
map系の関数を使いたくても構造体にはすんなり使えないといったように。
という事で、誰得?という気がしないでもないが、そんな事気にしてたら
common-lispのネタなんて書けないし、とりあえず、英語圏の人の
中には自分と同じような事を既に考えて質問している人もいたので、
ここに書いてみる事にした。
正直dirty hackだと思うけど、common-lisp自体が
それの塊みたいなもんだし、common-lisperの思想からは外れないかなと。
とりあえず動けばいいの、動けば笑
変換関数の定義
(defun struct->list (instance)
"構造体をリストに変換する関数"
(let* ((str (format nil "~s" instance))
(sexp (read-from-string (subseq str 2))))
(values (cdr sexp)
(car sexp)))) ; => STRUCT->LIST
(defun list->struct (name lst)
"リストを構造体に変換する関数"
(read-from-string (format nil "#S~s" (cons name lst)))) ; => LIST->STRUCT
他の言語だとどうか知りませんが、common-lispだとサクッと書けました。
使い方
では使い方の説明をば。
構造体を定義する
;; まず適当に構造体を定義
(defstruct person
name
job) ; => PERSON
(setf b2 (make-person :name "biofermin2" :job nil)) ; => #S(PERSON :NAME "biofermin2" :JOB NIL)
;; 例えばループで構造体のインスタンスの中身を表示しようとこんな事すると。。。
(format t "~{~a~}~%" b2) ; =>
;; エラーになって評価されません。TYPE-ERRORが出ました。
;; まぁ、format関数に構造体を突っ込もうなんて人はなかなかいないと思います。
構造体をリストに変換
それでは上の関数を使って構造体をリストに。
(setf b2-list (struct->list b2)) ; =>(:NAME "biofermin2" :JOB NIL)
PERSON
;; 多値としてリストの他に、構造体名も返しています。
(format t "~%~{:~a ~a~%~}" b2-list) ; =>
:NAME biofermin2
:JOB NIL
NIL
;; このようにformat関数でもちゃんと表示出来ます。
;; いちいち変数に入れなくてもダイレクトでももちろんいけます。
(format t "~%~{:~a ~a~%~}" (struct->list b2)) ; =>
:NAME biofermin2
:JOB NIL
NIL
;; 一応関数は挟んでますが、構造体をformat関数に突っ込んでます!
;; もちろんmapcarにも渡せます
(mapcar #'print (struct->list b2)) ; =>
:NAME
"biofermin2"
:JOB
NIL (:NAME "biofermin2" :JOB NIL)
リストから構造体に変換
では次は作成したリストを使って、構造体に変換してみましょう。
(setf b3 (list->struct 'person b2-list)) ; =>#S(PERSON :NAME "biofermin2" :JOB NIL)
インスタンスb3がちゃんと構造体になっているか動かしてみましょう。
(person-name b3) ; =>"biofermin2"
ちゃんと動きました。
ただ、b3もbiofermin2の名前じゃ、あれなんでbiofermin3に変えて、
草刈りの仕事を与えてみましょう。
common-lispの教科書的なやり方では
;; #1
(setf (person-name b3) "biofermin3"
(person-job b3) "草刈り") ; => "草刈り"
;; #2
(setf b3 (make-person :name "biofermin3" :job "草刈り")) ; => #S(PERSON :NAME "biofermin3" :JOB "草刈り")
だいたい書かれているのはこの上記2つのやり方だと思います。
ただ、ここまで来てお気付きの方がいるかもしれませんが、
もう1つやり方があります。
;; #3
(setf b3 #S(person :name "biofermin3" :job "草刈り")) ; =>#S(PERSON :NAME "biofermin3" :JOB "草刈り")
若干ですが、簡潔ですね。
それでは復習も兼ねて
いずれのスロットルもnilにしたい時は
(setf b3 (make-person :name nil :job nil)) ; => #S(PERSON :NAME NIL :JOB NIL)
(setf b3 (make-person)) ; => #S(PERSON :NAME NIL :JOB NIL)
(setf b3 #S(person)) ; => #S(PERSON :NAME NIL :JOB NIL)
一番下の方のやり方はお行儀が悪いかもしれませんが、結果はいずれも同じです。
汚いやり方でもいろいろ抽斗を持っておくのもいいと思います。
最後に
一応ソースコードはここにも貼っておきました。
https://gist.github.com/biofermin2/70830e362f1840b89c7158030a2b3b7a
構造体からリストに変換する関数は多値として構造体名も吐き出すので、
multiple-value-bindを使えば、その評価結果を使って更に構造体に
変換する事も出来るのですが、構造体(→リスト)→構造体と変換しても
何も嬉しくないのでそういう例は書きませんでした笑
正直あまりこういう事を必要とする人は少ないと思うんですけど、
構造体に対する面倒だなという心理的障壁が幾分でも下がれば
幸甚かなと。
Happy Hacking!
追記
ふと思い立ち教科書的な書き方も書いてみました。
(defmethod person-list ((p person))
(list :name (person-name p) :job (person-job p))) ; =>#<STANDARD-METHOD COMMON-LISP-USER::PERSON-LIST (PERSON) {10047011F3}>
(person-list b2) ; =>(:NAME "biofermin2" :JOB NIL)
(person-list b3) ; => (:NAME "biofermin3" :JOB "草刈り")
スロットが2つだけなので、そんなに大変でもないし、すっきりして見えますね。
普通にこっちのやり方の方がいいのかもしれないです笑
[2023-06-01]
昨日実用common lisp読書会参加してたら
構造体に見慣れないオプションをつけてるものを発見。
defstructでは型指定出来るけど、リストに指定すれば
リスト形式で出力される。
(defstruct (hoge (:type list))
slot1
slot2
...)
みたいな感じ。
という事で、struct->listみたいなものは不要という事。
やっぱ、common lisp凄いなぁ。
これを使えば、構造体チックな使い方が出来るリストが作れる。
なかなかおもしろい。