super super & sub sub の話
DESCRIPTION
Smalltalkのメソッド探索の仕組みを自在にいじってみるという話TRANSCRIPT
第27回 Smalltalk勉強会 - Smalltalkの超能力
2010 Tetsuya Hayashi2010 Masashi Umezawa
動機 Smalltalkのsuperpowers紹介なのでsuperに関わる何かをしたい
実際OOPSLA 2008のSmalltalk superpowers では Travis Griggs さんがsuper superの話をした模様
メソッド探索部分をいじる好例であろう
特徴
通常のsuper
メソッド探索を自分のスーパークラスから開始
super superと書くと… メソッド探索を自分のスーパークラスのスーパークラスから開始する
メッセージなので super super super superなどと書くことも可能
=> オブジェクト指向はさらに自由になったw
作り方
Travisさんの実装はVisualWorksで行われていたので、Squeak版を作った
ついでに調子に乗ってメソッド探索を自分のサブクラスから開始するsubも作った
使ったテクニックはほぼ同じ
割と素直な実装
テクニック (1) Object >> doesNotUnderstand: aMessage
理解できないメッセージを受け取ったときにVMから送信されるコールバック
aMessageにはもともと送られたメッセージが入る
Rubyだとmethod_missing
オーバーライドすると、メッセージを自分以外の誰かに受け流すことが可能
例: doesNotUnderstand:でプロキシ
プロキシの実装
MyLogProxy >> doesNotUnderstand: aMessageLogger log: aMessage.^ realObj perform: aMessage selector
withArguments: aMessage arguments
テクニック (2)
CompiledMethod
その名の通り、コンパイルされたメソッド
各クラスの持つmethoDictionaryに値として格納されている
○キーはメッセージセレクタのシンボル
aClass compiledMethodAt: selector で取り出せる
○もしくは aClass >> selector
バイトコードをいじったりもできるが、今回はおとなしく間接的な起動用に使う
例: CompiledMethodによるメソッド起動
通常の場合
ord := OrderedCollection with: 1 with: 2.ord add: 3.
ord := OrderedCollection with: 1 with: 2.method := ord class compiledMethodAt: #add:.method valueWithReceiver: ord arguments: #(3).
CompiledMethod経由では…
テクニック (3)
thisContext
実行中のコンテキストが入っている特殊変数
○ true, false, nil, self, superに次ぐ、第6の予約語
○約束事の少ないSmalltalkでは希有の存在
典型的にはデバッガの実装などで使われる
thisContext sender で送信者のコンテキストが得られる
○次々にたどっていくことも可能
例: thisContextでアクセス制御
privateメソッドの実装※
Object >> privateself class = thisContext sender sender methodClass
ifFalse: [SmallTalkAccessViolation private signal]
methodAself private....
使用例
※ SmallTalk R4.1 - http://smalltalk.smalltalk-users.jp
コードの解説(1)
AnySendというクラスを定義
ProtoObject subclass: #AnySendinstanceVariableNames: 'receiver searchClass'classVariableNames: ''poolDictionaries: ''category: 'SuperSuper'
receiver ->メッセージの受けとり手
searchClass -> メソッド探索の起点となるクラス
コードの解説(2) AnySendはProtoObject(Objectのさらに上)を継承
ほとんど全てのメッセージに答えられないため、doesNotUnderstand: が起動される
CompiledMethodを取り出して起動
doesNotUnderstand: aMessage^((searchClass whichClassIncludesSelector:
aMessage selector) >> aMessage selector)valueWithReceiver: receiverarguments: aMessage arguments
コードの解説(3) Object >> superを用意
AnySendをsuper:かsupersuper:で作る
thisContextで、直前でsuper(変数)を使っているか判断
super| context sendCode |context := thisContext sender.sendCode := context method at: context pc - 2.^(sendCode == 133)
ifTrue: [AnySend supersuper: self]ifFalse: [AnySend super: self]
コードの解説(4)
AnySend class >> self:
self: anObject^self receiver: anObject
searchClass: anObject class
生成用のクラスメソッドを定義
super: anObject^(self self: anObject) super
AnySend class >> super:
コードの解説(5)
supersearchClass := searchClass superclass
supersuper: anObject^(self super: anObject) super
もう少し生成用のクラスメソッドを
AnySend class >> supersuper:
AnySend >> superでsuperclassをずらす
以上。これで動く!!
デモ super superで真ん中のメソッドを飛ばす
調子に乗ってsubも追加(1)AnySend class >> sub:
sub: anObject^(self self: anObject) sub
AnySend >> sub
subsearchClass := searchClass subclass
subclass^self subclassAt: 1
Behavior >> subclass
調子に乗ってsubも追加(2)
Behavior >> subclassAt:
subclassAt: anIndex| subs |subs := self subclasses
ifEmpty: [self error: 'No subclass'].^subs at: anIndex
ifAbsent: [self error:'No subclass at: ', anIndex printString]
サブクラスは1つとは限らない(1)
methodA((self sub @ 2) sub @ 3) doSomething
2番目のサブクラスの3番目のサブクラスのdoSomethingを起動
とか書けるとクール!!
methodB((self sub << ClassA) sub << ClassB) doSomething
この際だし直接指定もやっておこう
サブクラスは1つとは限らない(2)
AnySend class >> sub
@ anInteger(searchClass inheritsFrom: receiver class)
ifFalse: [self error: '@ can be used after sub'].searchClass := searchClass superclass
subclassAt: anInteger
AnySend >> @
サブクラスは1つとは限らない(3)
<< aClass…"@と同じ"
searchClass := searchClass superclasssubclassClassOf: aClass
AnySend >> <<
subclassClassOf: aClass^self subclassAt: (self subclasses indexOf: aClass)
Behavior >> subclassClassOf:
デモ subとsuperを使って無限ループ
Top >> doSomethingLoopyself waitASecond.self doSomething: 'Hello'.self sub sub doSomethingLoopy.
Middle >> doSomethingLoopyself doSomething: '!!!!'.super doSomethingLoopy.
Bottom >> doSomethingLoopyself doSomething: 'World'.super doSomethingLoopy.
まとめ
Smalltalkでは、メソッド探索の仕方などは簡単にいじれる
クラスを一つ導入していくつかのメソッドを追加したのみ
新たな言語もすぐ作れる
まじめな開発ではsuper superとか使っていたらクビです