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 だけの話だったりしないですよね?