ProtoObject の謎と superclass

とにかくなにかしておかないと、いつまでたっても Smalltalk 使えるようにならないので、まず手始めとして squeak のクラスブラウザで探検を始めております。何も分からない初心者がまず最初にやってみることと言えばクラス階層山登りですよね。適当なクラスを選んで眺めてみて、飽きたらスーパークラスに移動、そして眺める、の繰り返しです。そうこうしているウチに結局行き着いたのは ProtoObject だったわけですが、これはなんともえへへな感じになっていて陽気です。

ProtoObject subclass: #ProtoObject
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Kernel-Objects'.
ProtoObject superclass: nil

え、ProtoObject のサブクラスが ProtoObject なんですか!それは珍妙で素晴しい!

でも、これって継承関係が循環しちゃわないのでしょうかと心配になるところですが、よく見たら "ProtoObject superclass: nil" って書いてあります。どういう事? ProtoObject のサブクラスは ProtoObject だって書いてあるのに、まるでそれを急転直下否定するかのように ProtoObject のスーパークラスnil ってなに?実際どうなのかを試してみました。

ProtoObject superclass. "=> nil"
ProtoObject subclasses. "=> {Object . ObjectOut . ImageSegmentRootStub . MessageCatcher}"

結局 ProtoObject のスーパークラスには nil が指定されていて、ProtoObject のサブクラスに ProtoObject は入っていません。じゃあどういうわけで最初に "ProtoObject subclass: #ProtoObject" だなんて書いているのでしょうか。とっても不思議です。というか後から superclass を指定できるのであれば、subclass で指定したものってどうなるの?という疑問が湧いてきます。

まずは ProtoObject のまねをして、適当なクラスを作成して superclass: nil してみます。

Object subclass: #ClassA
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MyClasses'.
ClassA superclass: nil

と書いて、accept してみると、びっくり!

ProtoObject subclass: #ClassA
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MyClasses'.
ClassA superclass: nil

と勝手に書き換えられちゃいました。なんて強引なんでしょうか。でもこれでなんとなく "ProtoObject subclass: #ProtoObject" の正体が少しだけ分かった気がします。もしかしたら最初はなんか他のクラスの subclass だったかも知れないけど、superclass: nil で勝手に ProtoObject に書き変わって万歳な感じなんですね。

ホント?

なにか不安を感じたので、まずそもそも循環するような継承関係を作れないのかを確かめてみました。

ClassB subclass: #ClassB
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MyClasses'

という感じで accept してみたら、やっぱりなんか色々怒られました。ClassBuilder の validateSuperClass が云々だそうですが、ということはメッセージsubclass の本体は ClassBuilder というものなんですね。ここで validate してるからエラーになっているわけで、逆に言うとひょっとしてこの ClassBuilder というものを通さないでクラスを作成したりなんかしたりしちゃえば、色々無理のあることが出来たりするのでしょうか。とにかく普通のやり方では循環する継承関係を作成するのは困難であるようであり、そこから推測するに "ProtoObject subclass: #ProtoObject" も普通でないのでしょうから、やはり "ProtoObject superclass: nil" というのはそういうエラーを避けるための裏技の一種なのでしょうか?

いやいや、でも結局 superclass が ProtoObject じゃなくて nil なわけだから、これはどうしたものでしょう。では次に nil に対して subclass してみます?だって superclass が nil なわけだし、ひょっとしてメッセージ subclass を送れちゃったりするわけでしょうか。

nil subclass: #ClassC
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MyClasses'

これを accept すると、またしても書き換えられちゃいました!なんか反則っぽいよ!

ProtoObject subclass: #ClassC
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MyClasses'

そんなわけでまたしても ProtoObject が登場しました。一応 nil について確かめたところ、これは UndefinedObject のインスタンスで、インスタンスメソッドとして subclass が存在しているのでメッセージを送れちゃうわけですね。でもなぜ ProtoObject に書き換えられてしまうのでしょうか。良く分かりませんが、とにかくこれで ProtoObject がなぜああいう書き方になっているのかについては想像が出来るようになりました。

nil subclass: #ProtoObject
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Kernel-Objects'.

直接このように書いてしまうと、これが書き換えらえて次のようになるはずです。

ProtoObject subclass: #ProtoObject
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Kernel-Objects'.

でもこれは循環した継承関係になってしまうだけでなく ClassB の例において確かめたように validate で色々怒られてしまうわけです。よってこれを避けるためには ProtoObject superclass: nil を使うしかないということですね。きっと。だから最初の "ProtoObject subclass: #ProtoObject" の部分は多分 "nil subclass: #ProtoObject" の nil が書き換えられた後の残骸に過ぎなくって、ということだろうと予測します。

では本題です。結局のところ Class superclass ってのが出来るのなら、subclass での指定を無視してこれで好きに superclass を設定出来ることになるわけですよね。というわけでやってみます。

Object subclass: #ClassD
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'MyClasses'

という ClassD を作成します。この ClassD に対して superclass を送ってみます。

ClassD superclass. "=> Object"
ClassD superclass: ClassD. "=> ClassD"
ClassD superclass. "=> ClassD"

これにより ClassD のスーパークラスが ClassD になってしまいました。おおあろうことかこれで循環した継承関係が出来てしまいました。じゃあ...

ClassD new asdfghjkl

としてみたら、

Recursive not understood error encountered

2034326876 Behavior>new
2034326472 UndefinedObject>?
2034302584 Compiler>evaluate:in:to:notifying:ifFail:logged:
2034306924 [] in ParagraphEditor>evaluateSelection
2034306424 BlockContext>on:do:
2034298236 ParagraphEditor>evaluateSelection
2034298144 ParagraphEditor>doIt
2034298420 [] in ParagraphEditor>doIt:
2034298052 Controller>terminateAndInitializeAround:
2034297960 ParagraphEditor>doIt:
2034297344 ParagraphEditor>dispatchOnCharacter:with:
2034297252 TextMorphEditor>dispatchOnCharacter:with:
2034297148 ParagraphEditor>readKeyboard
2034296964 TextMorphEditor>readKeyboard
2034290228 [] in TextMorph>keyStroke:
2034290136 TextMorph>handleInteraction:fromEvent:
2034290044 TextMorphForEditView>handleInteraction:fromEvent:
2034289900 TextMorph>keyStroke:
2034289808 TextMorphForEditView>keyStroke:
2034289716 TextMorph>handleKeystroke:
2034289348 KeyboardEvent>sentTo:
2034289256 Morph>handleEvent:
2034289164 Morph>handleFocusEvent:
2034289440 [] in HandMorph>sendFocusEvent:to:clear:
2034289532 [] in PasteUpMorph>becomeActiveDuring:
2034289072 BlockContext>on:do:
2034288980 PasteUpMorph>becomeActiveDuring:
2034288796 HandMorph>sendFocusEvent:to:clear:
2034288592 HandMorph>sendEvent:focus:clear:
2034287264 HandMorph>sendKeyboardEvent:
2034287172 HandMorph>handleEvent:
2034278244 HandMorph>processEvents
2034278360 [] in WorldState>doOneCycleNowFor:
2034278152 SequenceableCollection>do:
2034278060 WorldState>handsDo:
2034277968 WorldState>doOneCycleNowFor:
2034275992 WorldState>doOneCycleFor:
2034275900 PasteUpMorph>doOneCycle
2034032168 [] in >spawnNewProcess
2034032352 [] in BlockContext>newProcess

とか出て squeak が終了してしまいました。まったくもういきなり。

さてそろそろまとめますと、今回の教訓は、Smalltalk はこういうのなんでもアリ!ということです。クラスベースのオブジェクト指向言語ならば普通は継承関係が循環しないように作るものだろうと思いますが、Smalltalk であればそういう配慮を無効にすることすら出来てしまうわけですね。Rubyだとこうは書けないもんね。色々すごいなぁ、と思いました!

こんな感じで Smalltalk はとっても陽気で面白い言語ですね。でもひょっとしてこれって squeak だけの話だったりしないですよね?