nptclのブログ

Common Lisp処理系nptの開発メモです。https://github.com/nptcl/npt

forward-referenced-classとは何か

そんなクラス知らねえと言われても仕方がないほどマイナーなものです。
こいつは、だいたいはmop関連のパッケージに隠れています。
ANSI Common Lispには制定されていませんが、 CLOSを構築する際に必要となるので標準みたいなものでしょう。

まだあんまり読めてないんですが、 The Art of the Metaobject Protocolっていう専門書に載ってないんですよね。 検索しても日本語の解説は出てこない。 Common Lisp自体が古いから仕方がないといえば仕方がない。 じゃあ自分が書くか。

処理系依存ですが、次のようにすると確認できます。

sbcl

* (find-class 'sb-mop::forward-referenced-class)
#<STANDARD-CLASS SB-MOP:FORWARD-REFERENCED-CLASS>

ccl

? (find-class 'ccl::forward-referenced-class)
#<STANDARD-CLASS FORWARD-REFERENCED-CLASS>

clisp

> (find-class 'clos::forward-referenced-class)
#<STANDARD-CLASS FORWARD-REFERENCED-CLASS>

これは一体何なのかというと、defclassで先行して存在しないsuperclassを指定したときに 仮決めとして指定されます。

クラスを先行して設定した例をもとに説明します。

(defclass aaa (bbb) ()) ;; ★まだbbbは存在しない
(defclass bbb () ((hello :initform "zzz")))

(slot-value
  (make-instance 'aaa)
  'hello))"zzz"

では内部でどうなっているのか見てみます。
モップを使いますので、sbclならsb-mopuse-packageしてください。

* (use-package 'sb-mop)

なんか清掃用具みたいですね。

まずは綺麗な状態から、再びクラスaaaを作成します。

* (defclass aaa (bbb) ()) ;; ★まだbbbは存在しない
#<STANDARD-CLASS COMMON-LISP-USER::AAA>

この状態でsuperclassの内容を確認します。

* (class-direct-superclasses (find-class 'aaa))
(#<FORWARD-REFERENCED-CLASS COMMON-LISP-USER::BBB>)

存在しないはずのクラスbbbは、forward-referenced-classと出てきています。 この表記から、forward-referenced-classmetaclassだということがわかります。

ではクラスbbbを作成します。

* (defclass bbb () ((hello :initform "zzz")))
#<STANDARD-CLASS COMMON-LISP-USER::BBB>

superclassはどうなっているでしょうか。
ここは処理系によって分かれました。

sbclの例を示します。

* (class-direct-superclasses (find-class 'aaa))
(#<STANDARD-CLASS COMMON-LISP-USER::BBB>)

class-ofstandard-classに変わっています。
この時点で確定するってすごいですね。
どうやっているんだろう。
bbbfind-classreferenced-classとして登録していたのか、 あるいはclass-direct-superclassesの実行契機で見直したのか。

cclも確定していました。

? (class-direct-superclasses (find-class 'aaa))
(#<STANDARD-CLASS BBB>)

clispはまだ保留中です。

> (class-direct-superclasses (find-class 'aaa))
(#<FORWARD-REFERENCED-CLASS BBB>)

いずれにせよ、finalizeした時点で確定します。
classをfinalizeさせます。

> (make-instance 'aaa)
#<AAA #x000801C57458>

あらためてsuperclassを確認します。

> (class-direct-superclasses (find-class 'aaa))
(#<STANDARD-CLASS BBB>)

なるほど。

ということは、forward-referenced-classを持っている場合は 当然class-precedence-listは作成できない?

[1]> (defclass aaa (bbb) ()) ;; ★まだbbbは存在しない
#<STANDARD-CLASS AAA :INCOMPLETE>
[2]> (class-precedence-list (find-class 'aaa))

*** - The class #<STANDARD-CLASS AAA :INCOMPLETE> has not yet been finalized.
The following restarts are available:
ABORT          :R1      Abort main loop
Break 1 [3]>

finalizeしないとダメだそうです。
finalizeがされたかどうかの確認は次のようになります。

> (class-finalized-p (find-class 'aaa))
NIL

ちなみにclass-precedence-listの実行結果は、

  • clisp -> simple-error
  • sbcl -> unbound-slot
  • ccl -> nil

でした。
まあどれでもいいんじゃないでしょうか。

不完全なクラスはdefclassensure-classでは作成を途中で放棄し、 最大限まで仕事を遅らせて、make-instanceなんかでfinalizeする必要が生じたとき、 ようやく重い腰をあげることがわかりました。

めんどくさい!