SBCLとCCLの違い:defstructのincludeにおけるスロット名の比較方法
Common Lispの構造体定義マクロdefstruct
では、:include
で別の構造体のスロット名やその初期値を引継ぐことができます。このとき、下記の(a 100)
のように初期値を上書きできます。
CL-USER> (defstruct parent (a 10) (b 20)) PARENT CL-USER> (defstruct (child (:include parent (a 100))) c) CHILD CL-USER> (make-child) #S(CHILD :A 100 :B 20 :C NIL)
:include
内で存在しないスロット名を指定すると当然エラーになるわけですが、このときのスロット名の比較(存在するかを判定するための)の仕方がSBCLとClozure CL (CCL) *1で異なるようだったのでメモ。
このRoswellスクリプトを走らせると、SBCLではエラーなく動きますが、
#S(TEST-STRUCT2 :SLOT1 100 :SLOT2 NIL)
CCLではslot1
なんて知らんと言われてエラーになります(CLISPもこのパターンでした)。
Error: TEST-STRUCT has no SLOT1 slot, in (:INCLUDE TEST-STRUCT (SLOT1 100))
CCLで動かすためには、初期値を上書きする部分でpack-a
にintern
されたslot1
シンボルを指定する必要があります(pack-a
の定義でslot1
もexportするなど)。なお、exportしておく分にはSBCLでも問題なく動くので、必要な場合はexportしておくのが正しいと思います。
include
において、SBCLではパッケージに依存しない形でスロット名を比較しており、CCLではパッケージに依存した形で比較しているようです。…で終わるのも悲しいので、それぞれの該当部分のソースもほんの少し見てみます。
まずSBCLのソースです。手元にあったバージョン1.2.15を見てみました。エラーメッセージから該当箇所は簡単に見つかります。:test #'string=
と比較方法を指定しているので、確かにパッケージを含まないシンボル名のみで比較しているようです。
;; L768~@src/code/defstruct.lisp (defun frob-dd-inclusion-stuff (dd) ;; ...(略)... (flet ((included-slot-name (slot-desc) (if (atom slot-desc) slot-desc (car slot-desc)))) (mapl (lambda (slots &aux (name (included-slot-name (car slots)))) (unless (find name (dd-slots included-structure) :test #'string= :key #'dsd-name) (error 'simple-program-error :format-control "slot name ~S not present in included structure" :format-arguments (list name))) ;; ...(略)...
次はCCLです。バージョンは1.9です*2。同じくエラーメッセージから探してみます。named-ssd
なるマクロでスロットが存在するかを探していますが、最終的には下のようにeq
で比較をしています。ということで、確かにパッケージを含む形で比較を行っているようです。
;; L42~@lib/defstruct-lds.lisp (defmacro defstruct (options &rest slots &environment env) ;; ...(略)... (while slots (if (atom (car slots)) (setq name (%car slots) args ()) (setq name (%caar slots) args (%cdar slots))) (unless (symbolp name) (signal-program-error $XNotSym name)) (unless (setq ssd (named-ssd name slot-list)) (error "~S has no ~S slot, in ~S" (sd-name sub-sd) name (cons :include include))) ;; ...(略)...
;;L74@lib/defstruct-macros.lisp (defmacro named-ssd (name slot-list) `(assq ,name ,slot-list)) ;; L402~@compiler/optimizers.lisp (define-compiler-macro assq (item list) (let* ((itemx (gensym)) (listx (gensym)) (pair (gensym))) `(let* ((,itemx ,item) (,listx ,list)) (dolist (,pair ,listx) (when (and ,pair (eq (car ,pair) ,itemx)) (return ,pair))))))
SBCLはスロット名なんてパッケージ関係ないよねという考え方…かと思いきや、with-slots
やslot-value
ではやっぱりパッケージを考慮しているので、なんだか半端です。この辺りも考え合わせると、SBCLのやり方が特殊という感じがします。どういった考えなんでしょう。