Gauche:MOP:クラスオブジェクトのchange-class

Gauche:MOP:クラスオブジェクトのchange-class

Shiro(2011/06/08 18:44:01 PDT): Gauche:Bugsより:

teppey(2011/05/29 17:56:03 PDT): メタクラスがどういうものかよく分かっていないのですが、以下のスクリプトがsegmentation faultになりました。

$ uname -srm
Linux 2.6.32-5-amd64 x86_64
$ ./gosh -V
Gauche scheme shell, version 0.9.1 [utf-8,pthreads], x86_64-unknown-linux-gnu
$ cat /tmp/x.scm
(define-class <c-meta> (<class>) ())
(define-class <c> () () :metaclass <c-meta>)
(define c (make <c>))
(change-class <c> <c-meta>)
(write c)
$ ./gosh -ftest /tmp/x.scm
zsh: segmentation fault  ./gosh -ftest /tmp/x.scm

SEGVの直接の原因は、デフォルトのchange-classメソッド (実質はchange-object-class 手続き) が、builtinスロット(Schemeレベルで定義されたスロットではなく、 Cの構造体メンバがSchemeスロットとして見えているもの) をコピーしない、 というものだった。<class>オブジェクトの重要なスロットであるcplとか accessorsとかはbuiltinスロットなんで、これらがコピーされないと change-classした後の<class>オブジェクトは不完全で、 それを不完全なまま使おうとするとSEGVる。

で、そこを直して、クラスオブジェクトのchange-classについて若干 カスタマイズしてやれば上のコードは動くことは動くんだけど、 どう動くのが正しいか、ということについて考え出すとはまった。

Change-classの意味

インスタンスのchange-classは、

というものだ。インスタンスの振る舞いや構造を決めるのはクラスなので、 クラスが変わればそれらが変わる、というのは意味的に納得できる。

で、構造を変えるためには、インスタンスの「変える前のクラス」と「変える先のクラス」 を比べて、例えば共通するスロットの値は持ち越すとか、 新しく追加されたスロットは初期化するとか、そういう操作が必要になる。

クラスオブジェクトはメタクラスのインスタンスなので、 その点ではインスタンスのchange-classと変わることはない。 つまり、クラスのidentityは変わらずに、その振る舞いや構造が 新たなメタクラスで指定されるものに変わる。

ここまでは何も問題がない。

ただ、クラスオブジェクトの場合、そのクラスから作られたインスタンス が既にあるかもしれない。そして、クラスオブジェクトが変化するということは、 既存のインスタンスの振る舞いや構造も変わる可能性がある。

だから、概念的には、クラスオブジェクトがchange-classされたら、 それはクラス再定義と同じようなことになると考えられる。 クラス再定義が起きると、既存のインスタンスはlazyに「前のクラス」から 「新たに定義されたクラス」へとchange-classされる。

ここが問題。クラス再定義の場合、「前のクラス」と「新たに定義されたクラス」は オブジェクトとしては別のものだ。だからchange-classが使える。 ところが、クラスオブジェクト自身がchange-classされた場合、 クラスオブジェクトのidentityはそのままで、情報が上書きされる。 「前のクラス」と「新たに定義されたクラス」の両方を 見比べることができない。

インスタンスがchange-classしようにも、そのインスタンスの現在のクラスと、 変えるべき先のクラスは、全く同一なのだ。

オプション

策はいくつか考えられる。それぞれについて帰結を考え中。

とりあえずコミット (4989fa2)

More ...