インスタンス変数のオーバーロード


Matz にっきに、

多重継承のある言語としてRubyを見ると

という二点はかなり痛い。
前者は継承に参加する全クラス(モジュール)間で名称に衝突があってはいけないことを意味する。単純継承言語ではこの条件はあまり厳しくない。スーパークラスへのラインは一本しかないので調整が難しくないからだ(にも関らずSmalltalkではインスタンス変数はクラスごとに独立だったはず)。

とあるのを見かけて、最後の但し書きを、つまり Smalltalkインスタンス変数のオーバーロード(多重定義)が可能である…ということだと解釈したので、そんなことわぁ〜 Smalltalk にはできんやろぉ〜、ちっちきちぃ〜…とかつぶやきつつ、鼻歌まじりに VisualWorks で試してみたら(警告は受けますが)あっさりできてしまい、ぎゃふん。orz 以下はそのメモ。




まず、インスタンス変数 instVar を持つクラス Super を定義。ここでは Class メニュー → New Class... を使うとアクセッサの定義とかをやってくれるのでお手軽。いってみればこれは、Ruby における attr の GUI 版みたいなものですね。


次に、同名のインスタンス変数 instVar を持つクラス Sub を、Super のサブクラスとして定義。でも、New Class... はインスタンス変数にスーパークラスのと重複があると OK ボタンをアクティブにしてくれません。しかたがないので 20 年来の方法、つまり、ブラウザでクラス Super をクリックして選択後、再びクリックして選択を解除したときにコードペイン(下のペイン)に現れるクラス定義のテンプレートを Sub の定義に書き換えて accept(ctrl + s。alt や cmd を主に使う Squeak と異なり VisualWorks では、コマンドショートカットの多くは ctrl との組み合わせになっています)。

Smalltalk defineClass: #Sub
   superclass: #{Super}
   indexedType: #none
   private: false
   instanceVariableNames: 'instVar'
   classInstanceVariableNames: ''
   imports: ''
   category: '(none)'


システムに注意をうながされますが、わかってやっていることなので OK してひるまず先に進みます。


無事(?)、クラス Sub がブラウザ画面に登場。


クラスがそろったところで、適当な場所(通常はワークスペース。デフォルトでは起動時、画面左上にあるトランスクリプトウインドウの、右端のボタンで開くことができます)で、Sub new してこれをインスペクトしてみましょう。


instVar が2つあることが確認できます。


オーバーロードといっても、スーパークラス Super から継承したほうのインスタンス変数 instVar にはサブクラス Sub のメソッドにおける通常の記述ではアクセス不可なので、オーバーライドと変わらないのですが…。


クラス Super から継承したセッター(Super >> #instVar:)を a Sub から起動すると、どちらのインスタンス変数に値が束縛されるのか試してみましょう。インスペクタの右上のアイコンボタン(Toggle Evaluation Pane)をクリックしてインスペクタワークスペースを開いて、self instVar: #before を do it(ctrl + d)します。結果は、Super から継承した instVar(インスペクタで上段のほう)に #before が束縛されます。


これは、たとえ a Sub へのメッセージングの結果として起動された場合であっても、メソッド「Super >> #instVar:」のスコープにおいて instVar は相変わらず Super から継承した instVar のほうだということを示唆しているように見えます。


が、実のところは、スコープもなにも、Super >> #instVar: のコンパイル時にコンパイラは、Super のサブクラスにいかなるインスタンス変数が新たに(それが同名であろうとなかろうと)宣言されていようとそれについて関心を持つことはない…ということと、さらに加えてバイトコードレベルではインスタンス変数へのアクセスは名前では行なわれていない…というだけの話なのです。総じて、くだんの「instVar: #before」というメッセージ送信を受けての a Sub の挙動は、いたって自然なものと言えそうです。


Super に定義された instVar のセッター(Super >> #instVar:)のバイトコードは、次のようにして簡単に確認できます。インスタンス変数はその名前ではなく、インデックスで指し示されていることを見て取ることができます。

(Super compiledMethodAt: #instVar:) symbolic
=> '...
   1 <10> push local 0
   2 <58> store inst 0; pop
   3 <60> push self; return'


次に改めて、Super とまったく同じアクセッサを Sub にも再定義してみます(なお、Squeak と異なり VisualWorks では、メソッド定義の前にメソッドカテゴリペインで適当な名前のカテゴリを定義しておく必要があるようです)。引き続き同じインスペクタを使えばいいので、ゲッター(Sub >> #instVar)は不要なのですが、まあ、お約束なので。

Sub >> instVar
   ^ instVar
Sub >> instVar: anObject
   instVar := anObject



そして再び a Sub を束縛しているインスペクタに戻り、こんどは self instVar: #after と引数を変えて do it(ctrl + d)します。すると、#after は、Sub で宣言したほうの instVar(インスペクタで下段のほう)に束縛されます。



ちなみに古典的な Smalltalk である Squeak の場合どうかというと、スーパークラスで宣言済みのインスタンス変数を含むサブクラス定義時に、ノーティファイアにより完全に拒絶されてしまいます。しかし、いったんスーパークラスのものと重複しない仮の名前で宣言しておき、定義完了後に、

Sub instVarNames at: 1 put: 'instVar'

などとしてインスタンス変数の名称を変更することで、同様のことは可能なようです。