こんにちは。この講義では リフレクションを見ていきます。
Pharo 自身の中を見て リフレクションの利用法を見ていきましょう。
リフレクションは
大きく 2 つに分類できます。
イントロスペクションは
プログラムが自身の状態を観察する 能力のことです。
インターセッションはプログラムが 自分自身を修正する能力です。
レイフィケーションは
自身の状態や実行を変更するために
通常は暗黙になっているものを 明示化する概念です。
例えば、Pharo では
実行スタックは明示的です。 古典的なオブジェクトとして表現されます。
Pharo ではクラスも 完全に古典的なオブジェクトです。
他の言語には クラスはオブジェクトではないものもあります。
リフレクションのあるシステムでは
それ自身の表現を得ることができます。
それ自身を表現することができ
その表現を使ってそれ自身を変更できます。
表現を変更した時 そのシステムの状態を変更します。
それを因果関係と呼びます。
表現と状態の間の因果関係です。
状態を変えると
システムの状態だけでなく システムの表現も変更されます。
何がうれしいのかと言うと
オブジェクトの中身を見るために
このイントロスペクションと インターセッションを使うのです。
コレクションを 1 つ定義します。 OrderedCollection です。
そして Pharo のインスペクターという 道具を使って
オブジェクトの内側を見ることができます。
OrderedCollection クラスの インスタンスオブジェクトが
ここにありますが 中に、インスタンス変数があります。
array と firstIndex と lastIndex です。
このオブジェクトのインスタンス変数の値は
1 と 12 と そして 12 要素の配列があります。いいですね？
このインスペクターというツールは
一体どうやってオブジェクトの内側を みることができるのでしょう？
オブジェクトの内部状態を どうやって見るのでしょう？
インスペクターはイントロスペクション メソッドを使ってオブジェクトの内側を見ます。
イントロスペクションメソッドは Object で沢山定義されています。
instVarAt: でインスタンス変数を
整数インデックスで読むことができます。
これでこのオブジェクトの インスタンス変数 1 番を見れます。
このオブジェクトのインスタンス変数 1 番に 新しい値を与えて変更することができます。
あるいはインスタンス変数名でアクセスしたり
その値を変えることもできます。 instVarNamed:put: です。いいですね？
ここに例があります。 通常の方法で点を生成して
その点というのは 10@3 ですが
instVarNamed: 'x' として
この点のインスタンス変数 x の値を得ます。
得られる値は 10 です。
そして instVarNamed: 'x' put: 33 で
この点のインスタンス変数 x の値を
10 から 33 に変更します。
イントロスペクションと インターセッションを使って
オブジェクトの内部状態を見てきました。
instVarNamed:put: はインターセッションです。 いいですね？
ここで重要なことは カプセル化が破られているということです。
外部のオブジェクトがこのオブジェクトの 内部状態を変更しました。
これはカプセル化に違反しています。 しかし、とても有用です。
ツールを作ったり、それを使って 開発するのに便利です。
アプリケーションの通常のコードで 使うようなものではありませんが
開発ツールを構築する上では 強烈にパワフルなのです。
コードインスペクターが良い例です。
イントロスペクションの別の例を お見せします。
オブジェクトのクラスを得る class メソッドです。Object で定義されています。
この文字列にクラスを尋ねると String クラスが得られます。
この点にクラスを尋ねると Point クラスが得られます。
Smalltalk class Class クラスのクラスを尋ねることもできます。
などなど
基本的にオブジェクトに class メッセージを送ることで
そのクラスを知ることができます。
システムに問い合わせるメソッドは 沢山あります。
ここで、OrderedCollection に 沢山の問い合わせメッセージを送っています。
全てのスーパークラスを得たり
全てのセレクターを得たり
全てのインスタンス変数名を得たり
全てのサブクラスを得たり
そのコードの全ての行を得るなど…
これらの問い合わせメッセージを使って システムナビゲーションなどの
トップレベルツールが作られています。
システムナビゲーションは システムをブラウズするためのツールです。
例えば , メソッドを実装している
全てのクラスを見ることができます。
こんな風にウィンドウが開いて
, の全てのインプリメンターが得られます。
AbstractFileReference クラスが , メソッドを実装しています。
このメソッドを実装している 全てのクラスのリストが得られます。
もう 1 つ例があります。
メニューやボタンを
クリックすると、背後にあるオブジェクトに メッセージが送られるようにしたいとします。
メッセージ名はボタンの名前を使います。 ここで
文字列をオブジェクトに送るメッセージに 変換するにはどうしたらいいでしょう？
そのためのインターセッションメソッド があります。Object の perform: です。
引数としてシンボルを渡して 送りたいメッセージの名前とします。
するとこのオブジェクトに このメッセージが送られます。
同種のメッセージに perform:with: があります。
実行するメソッド名をシンボルとして与えて
引数を渡します。
例えば 古典的なメッセージ送信での 5 factorial を
リフレクションメッセージで行うためには
5 perform: #factorial とします。 オブジェクト 5 に
#factorial メッセージを実行してください とお願いをしています。
通常のメソッド探索が行われて実行されます。 つまりこれら 2 つは同じです。
もう 1 つ例があります。
コードインスペクタで
OrderedCollection クラスの内側を 見ることができます。
このクラスには属性があります。
例えば、 インスタンス変数 methodDict があります。
OrderedCollection の methodDict は
Behaviour からきています。 この methodDict には
MethodDictionary のインスタンスがきて CompiledMethod を持っています。
そういうわけで、このメソッド辞書に
このコンパイル済みメソッドがあって 別のコンパイル済みメソッドもあり
沢山のコンパイル済みメソッドが あることがわかります。
これらのコンパイル済みメソッドに ソースコードを尋ねることができます。
self getSource とすると
そのコンパイル済みメソッドの ソースコードが得られます。
更に
コンパイル済みメソッドを 直接実行することもできます。
valueWithReceiver:arguments: メッセージを使います。
ただし、注意してください。 この方法はメソッド探索は行われません。
コンパイル済みメソッドがあれば
メッセージを介さずに直接実行できるのです。
ここで、Integer クラスから
コンパイル済みメソッド factorial を得て
それにメッセージを送ります。 valueWithReceiver:arguments:
引数として、レシーバーに 5 を指定して
引数は空です。単項メッセージなので。
すると結果が得られます。
メソッド探索なしにコンパイル済み メソッドを実行しました。
まとめると リフレクションは極めて強力です。
イントロスペクションで
システム全体を見てみました。 システムがそれ自身の中で
オブジェクトとして表現されます。
そしてシステムの状態を変更 することもできます。
これを使って全てのオブジェクトに 汎用的な方法でツールを構築できます。
全てのオブジェクトに共通する 構造を持つプロトコルで
オブジェクトと対話することができます。
ただし、カプセル化に違反していることに 注意してください。
アプリケーションコードに使うものではなく ツール作りに使うためのものです。
コードインスペクターは リフレクションを使って作られています。
また、Pharo でリフレクションが どう実現されているか見ることができます。