この講義では高度な機能を もう 1 つ探検します。
オンデマンドで実行スタックを
オブジェクトに変換する方法を 見せたいと思います。
あまり細部には触れません。 直感的な道具立てとするのが目的です。
スタックを操作して ナビゲーションしたり
変更したりできますが 今回はナビゲーションに焦点を合わせます。
この話題についての読み物として 本の中で 2 つの章があります。
ブロックに関する章と
例外に関する章が Deep into Pharo という本にあります。
明快な説明がされていて 読む価値があるものです。
Pharo では Context クラスも スタックを表現します。
さてと
デバッガの中でコーディングする シナリオを使います。
ユニットテストを実行した時に
送ったメッセージが未定義のために 見つからなかったとします。
そのメソッドを作るように デバッガに言って
コンパイルして クラスにインストールします。
そして再実行します。
このメソッドは例外を上げます。 このシステムは魔法ではないので。
shouldBeImplemented メッセージが
デバッガでそのメソッドを編集するように 促してきます。
そのメソッドを実装して
その場で再コンパイルして 「Proceed」を選んで
プログラムを継続実行させます。
このシナリオには 2 つのキーポイントがあります。
1 つ目は、メソッドをオンザフライで 再コンパイルしたこと。
何度もやりますが それはコンパイラの仕事です。
2 つ目は、これが重要ですが 実行スタックを書き換えて
スタック上に 新しいチャンクを挿入します。
そうすることでエラーの後でも プログラムを継続実行できます。
単にスタックを具体化（レイフィケーション） してオブジェクトにするだけでなく
形の上での演習でもなく ユーザ体験を向上させたり
新しいツールを作ることができます。
ウェブアプリケーションを作る Seaside でも使われています。
Pharo は通常は C のスタックを持っています。 仮想機械のスタックです。
オンデマンドで、これを ライブなオブジェクトに変換します。
興味深いのは、これを使って ナビゲーションしたり修正できるのです。
ここでいう修正とは この Pharo のオブジェクトを変化させると
直接手の届かない C のスタックも変化させる とても強力なものです。
例外処理にも対応しています。 ぜひこれらの章を読むことを勧めます。
例外に対してスタックを辿っていって
キャッチブロックを探します。
いわゆる例外ハンドラです。
さらに、スタックをオブジェクトに 変換する能力は
Scheme のような 関数型言語にある継続や
ウェブサービスを可能にします。
どうやって動くのか説明するために thisContext と呼ばれる変数を見ます。
Pharo の 3 つの擬似変数の 1 つです。
擬似変数は self と super と thisContext です。
thisContext の値を得ようとすると 実行スタックが返ってきます。
デバッガを開くと見えるものです。
実行スタックは thisContext に基づいて表示されます。
halt を挿入した
メソッドを定義すると
デバッガが開きます。 しかし、thisContext と入力して
インスペクターを開いても 実行スタックそのものが開きます。
thisContext を使う例を
2 つお見せしましょう。
1 つ目はデプリケーションです。 API を変更したい時に使います。
プログラマはメソッドで deprecated: メッセージを
on:in: 付きで使います。 例外の講義で説明しました。
ここでは、「Use bar」という メッセージを示したいわけですが
デプリケーションは
ユーザに
「Message foo is deprecated in Pharo.」 と表示します。
ここで大事な点は
どのメソッドがデプリケーションの対象か 宣言しなかってにも関わらず
deprecated: を呼び出したのは foo だとシステムが特定したことです。
見ての通り、deprecated:の引数には メソッドを呼び出している
foo は与えられていません。
では、どうやって実装されているのでしょう？
deprecated: メッセージは
Deprecation 例外をあげます。
引数として anExplanationString などを渡します。
そして、この表現式が追加されています。 thisContext sender method
thisContext は deprecated: メソッドを 実行中のスタックです。
そして sender を使って メソッドを呼び出した側にアクセスできます。
すると foo が得られます。 つまりこの例題です。
そしてメソッドを求めます。
thisContext sender method はコンパイル済みのメソッドを返します。
それはオブジェクトで
A>>foo という名前です。
こうしてこの例外は メソッドのセレクタを得て
より明確なメッセージを作ります。
プログラマにそのメッセージの元を ハードコードさせることなく
ユーザーが理解しやすい メッセージを作ることができます。
では、もう 1 つの 強力な機能をお見せしましょう。
デバッグをしている時には 激しく使われているメソッドに
ブレークポイントを挿入することが よくあります。
システム全体を止めることなしに
自分のプログラムだけをデバッグ したいわけです。
haltOnce のような条件は システムを一度だけ停止させますが
やりたいことはそうではなく そのメソッドがある別のメソッドから
呼ばれた時だけ halt したいわけです。
プログラマは どう表現したらいいでしょう？
testSetInitialized メソッドから
foo が呼ばれたならば halt というわけです。
どう実装されているのでしょう。
通常、このメソッドは停止してはいけません。
Pharo でコードを開いて どう実装されているか見ることができます。
Halt は例外クラスです。
if: メッセージを見ます。
引数の場合分けがいくつかあります。
シンボルの場合について まずシンボルかどうか問い合わせた上で
コールチェインの中にそのシンボルが 含まれていないかを見ます。
では確認してみましょう。
そのメソッドを見てみます。
testSetInitialized という名前の
テストがあるとします。
その名前がここの引数です。
まず、メソッドは実行スタック
つまりコンテキストを引き出します。
コンテキストを使って スタックにアクセスします。
今は実行スタックの トップではありません。
トップでは呼び出す側の センダーがありません。
スタックのトップでは センダーは nil になります。
センダーが nil ではない場合には
スタックを移動していきます。
スタックの領域を絵に描くと こんな感じになっていて
sender を使って 上のほうにいきます。
1 つずつ。
ここでセレクターを 見ていく必要があります。
この領域のどこかに テストがあるはずです。
もしスタックのコールシンボルが
停止対象にしたいものと マッチしているか見ていきます。
testSet…から呼ばれていないか チェックしていきます。
該当していたら、そこで signal します。 このクラスは例外クラスですから。
重要なのは、スタックの具体化を しない言語でこれを実装することは
難しいということを 理解することです。
ここでは 5 行です。理解するのは 大変そうに見えるかもしれませんが。
コンパクトで強力で 具体化を通してのみ可能なのです。
thisContext は高度な機能なので 多くは使われません。
しかしツールなど新しいものを 創造する上で重要なものです。
これらのテストで見たように
この言語ではサポートされています。
継続を表現するためにも使われます。
Seaside を創った人は 継続を表現するために
スタックを操作するために Pharo の先祖にあたる言語を使いました。
これは Seaside のコールアンサーの 基礎になっています。
Pharo のこの高度な機能を いじり回してみましょう。