読者です 読者をやめる 読者になる 読者になる

is-a?を追いかける(MOPの片鱗をみた?)

Reading Gaucheにて現在is-a?相当の命令の辺りを読んでいます。
今は大体読み終わった段階なのですが、以下のコードの辺りの理解がいまいち(ひらメソッドで読んだページはReading Gauche/class.c/Scm_GenericChangeClass - Mona OS developers Wiki)。特にk->redefinedがfalseになるタイミングはいつなのかというのが課題になっています。

ScmObj Scm_VMIsA(ScmObj obj, ScmClass *klass)
{
    ScmClass *k = Scm_ClassOf(obj);
    if (!SCM_FALSEP(k->redefined)) {
        void *data[2];
        data[0] = obj;
        data[1] = klass;
        Scm_VMPushCC(is_a_cc, data, 2);
        return instance_class_redefinition(obj, k);
    }
    return SCM_MAKE_BOOL(Scm_TypeP(obj, klass));
}

結論からいうと、「新しく定義されたクラスのredefinedはfalseになっていて、instance_class_redefinition(obj, k)でobjのクラスを新しいのに置き換えたためfalseになる」。


新しいクラスを定義するところは恐らく以下の関数

/* Allocate class structure.  klass is a metaclass. */
static ScmObj class_allocate(ScmClass *klass, ScmObj initargs)
{
    ScmClass *instance = SCM_ALLOCATE(ScmClass, klass);
    SCM_SET_CLASS(instance, klass);
    instance->allocate = NULL;  /* will be set when CPL is set */
    instance->print = NULL;
    instance->compare = object_compare;
    instance->serialize = NULL; /* class_serialize? */
    instance->cpa = NULL;
    instance->numInstanceSlots = 0; /* will be adjusted in class init */
    instance->coreSize = 0;     /* will be set when CPL is set */
    instance->flags = SCM_CLASS_SCHEME; /* default */
    instance->name = SCM_FALSE;
    instance->directSupers = SCM_NIL;
    instance->accessors = SCM_NIL;
    instance->cpl = SCM_NIL;
    instance->directSlots = SCM_NIL;
    instance->slots = SCM_NIL;
    instance->directSubclasses = SCM_NIL;
    instance->directMethods = SCM_NIL;
    instance->initargs = SCM_NIL;
    instance->modules = SCM_NIL;
    instance->redefined = SCM_FALSE; /* <- ここ ***********************************/
    (void)SCM_INTERNAL_MUTEX_INIT(instance->mutex);
    (void)SCM_INTERNAL_COND_INIT(instance->cv);
    instance->data = NULL;      /* see the above note on the 'data' member */
    return SCM_OBJ(instance);
}

他にもredefinedにSCM_FALSEを代入してる所はあるのでここじゃないかもしれないし、ここ以外の入り口もあるのかもしれない。

至った道のりのメモ

grepでredefinedを探して、エディタとかで読んでいたら Gauche-0.8.11/test/object.scmで

(test* "simple redefinition of <x>" '(#t #f #t #f)
       (list (eq? (ref <x>-orig 'redefined) <x>)
             (ref <x> 'redefined)
             (eq? (ref <y>-orig 'redefined) <y>)
             (ref <y> 'redefined)))

ってのを発見。ここら辺でScm_VMIsAのkってクラスなのだという事をやっと意識し始める。
Gaucheのマニュアルなどを見ながらgoshでいろいろ試す。以下のはそれなりに分かってきてあんまり無駄なく動かしてみた例。

% gosh 
gosh> (define-class <X> () ()) ; Xというクラスの定義
<X>
gosh> (define <X-copy> <X>) ; Xの別名を用意(Xの実体との束縛をもう一つ)
<X-copy>
gosh> (define obj1 (make <X>)) ; Xのインスタンス作成
obj1
gosh> (define obj2 (make <X-copy>)) ; X(X-copy)のインスタンス作成
obj2
gosh> obj1
#<<X> 0x91e0b40>
gosh> obj2
#<<X> 0x91e0960>
gosh> (ref <X> 'redefined) ; <X>のもつredefinedはfalse
#f
gosh> (ref <X-copy> 'redefined) ; <X-copy>のもつredefinedはfalse
#f
gosh> (define-class <X> () (x)) ; <X>を再定義
<X>
gosh> obj1
#<<X>:redefined 0x91e0b40> ; なんか :redefined ってのが追加された
gosh> obj2
#<<X>:redefined 0x91e0960>
gosh> (ref <X-copy> 'redefined) ; <X-copy>を再定義したものは<X>(何か変な感じ)
#<class <X>>
gosh> (ref <X> 'redefined) ; <X>のもつredefinedはfalse
#f
gosh> (is-a? obj1 <X-copy>) ; obj1は<X-copy>(実体は前のX)のインスタンスじゃないよ(クラスが再定義されたから)
#f
gosh> (is-a? obj1 <X>) ; obj1は<X>のインスタンス
#t
gosh> obj1
#<<X> 0x91e0b40>
; :redefined が消えた(is-a?の中で処理されてる。Scm_VMIsAの instance_class_redefinition がそれ
gosh> (is-a? obj2 <X-copy>)
#f
gosh> obj2
#<<X> 0x91e0960>
gosh> <X>
#<class <X>>
gosh> <X-copy>
#<class <X> (redefined)>
gosh> (ref <X> 'redefined)
#f
gosh> (set! (ref <X> 'redefined) <X>) ; redefinedは読み込み専用
*** ERROR: slot redefined of class #<class <class>> is read-only
Stack Trace:
_______________________________________

MOPの片鱗を見た気がしないでもない。redefinedがfalseになるより新しいクラスを指させる仕組みの方が不思議というが凄いなと感じる。そこら辺は取り合えず追っかけない。
辺りを理解できれば解決しそうな気がする。