第18回
■
■SqueakではじめるSmalltalk入門 第18回
■
本連載では、名前は知っていてもなかなか触れる機会のないSmalltalkについて、最近話題のSqueakシステムを使って紹介しています。今回はノーティファイアで表示される情報の読み方を取り上げます。
メソッド#deposit:が定義された今、a BankAccountは、「deposit: 100」というような預金額を模した数値をパラメータに持つメッセージを受け取って期待される動きをすることができるようになりました。これは前回、インスペクタで束縛しているa BankAccountで確認しています。ただ、このチェックには、ちょっとした落とし穴があります。たとえば、あらためて、どこか適当な場所で、
BankAccount new deposit: 100
をdo it (cmd+D)してみましょう。別のBankAccountのインスタンスを作り、それに「deposit: 100」を送信しているだけです。結果、俗に“エラー”と呼ばれるノーティファイア(ピンクの横長のウインドウ)が現われて処理は中断してしまうはずです。
[fig.A]ノーティファイア
なぜ、このようなことになるのかをノーティファイアに表示される情報をもとに、解明してみましょう。
まず、BankAccountに送信されるnew、その結果返されるa BankAccountに改めて送られる「deposit: 100」も、いずれも不正なメッセージでないことについてはよろしいかと思います。では、なにゆえエラーが起こったのか。そのヒントとなる情報がノーティファイアのタイトルバーに表示されています。
MessageNotUnderstood: UndefinedObject>>+
これは、UndefinedObject(のインスタンス)は、#+(というセレクタを含むメッセージ)を理解できません…という意味です。UndefinedObjectのインスタンスは、nilです。nilは、UndefinedObjectの唯一のインスタンスです。つまり、BankAccountに対する「new」か、その返値に対する「deposit: 100」というメッセージ送信の結果、派生的に生じるメッセージ送信カスケードのどこかで「nil + …」という式が評価されたことが分かります。もちろん、あやしいのは我々が定義した後者ということになりますが、ノーティファイアにより提示された情報からも、それを確認することができます。
ノーティファイアのウインドウ内部、デバッガ起動などのための3つのボタンを用意したペインの下のリストに目を向けてみましょう。各行は、このノーティファイアを表示するまでに送信されたメッセージの履歴を表わしています。Squeakを含めたSmalltalkシステムは、一般に、バイトコードを実行することで動作し、そのバイトコードインタープリタにはスタックマシンが使われています。このリストは、その各スタックに積まれたコンテキスト(インタプリタの内部状態)の一覧、というふうに解釈することもできます。いずれにせよ、今のところは、どういう経緯でノーティファイアの表示に至ったのかを、このリストで簡単に把握できるというふうに考えておけばよいと思います。
UndefinedObject(Object)>>doesNotUnderstand: #+
BankAccount>>deposit:
UndefinedObject>>DoIt
Compiler>>evaluate:in:to:notifying:ifFail:
余談ですが、同様の情報を含んだものがノーティファイアの起動と同時に、仮想イメージと同じフォルダにSqueakDebug.logとして出力されています。Squeakシステムを維持できない致命的な障害を生じさせてしまったときは、いったん環境を抜けて、こちらを参考にするとよいでしょう。
1行目は、a UndefinedObjectが#doesNotUnderstand:メソッドを起動したことを表わします。括弧内のObjectは、実際にこのメソッドが定義されているクラス(メソッドホルダ)がUndefinedObjectではなく、Objectであることを表わしています。これは、ノーティファイアを起動するためのメッセージ送信です。一般にオブジェクトは自らが理解できないメッセージを受け取ると自身に改めて「doesNotUnderstand: …」というメッセージを送信する決まりになっています。なお、これに似た機構はCocoaでも採用されています。
本題の2行目はいったん飛ばして、参考のため、4行目と3行目についても簡単にコメントしておきましょう。do it (cmd+D)という操作によって選択文字列は、エディタなどのGUI関連オブジェクトを介して、コンパイラに渡されます。4行目のa Compilerに対するメッセージ送信がこれに当たります。このコンパイルの結果、選択文字列はバイトコードの関数(つまりメソッド)になるのですが、残念(?)ながらSmalltalkの世界ではクラスに属さない関数(つまりメソッド)の存在を許していません。そこでコンパイラはとりあえず、その文脈で擬変数selfに束縛されているオブジェクト(たいていはnil)のクラス(nilならUndefinedObject)に一時的に#DoItという名前のメソッドとして登録することで、その場をしのぎます。
3行目は、#DoItメソッドを登録したUndefinedObjectのインスタンスで擬変数selfに束縛されているnilに、改めて「DoIt」というメッセージを送信したことを表わします。この評価の後、#DoItメソッドは自動的に削除されます。do itやprint itなどのGUIを介した式の評価はあまりに手軽なので、一見、コードを直接インタープレットしているように思えるのですが、実際には、メソッドの定義とその起動のときと、まったく同じ手順(コンパイル、クラスへの登録、クラスに属するインスタンスへのメッセージ送信)を踏んでいることは、とても興味深いことですね。
さて、問題の2行目に戻ります。2行目は字面通り、そして当初の予想通り、a BankAccountに「deposit: 100」が送信されたことを表わしています。このことから、nilが自身に「doesNotUnderstand: …」というメッセージを送るはめになったのは、このコンテキストが原因であることを改めて確認できます。BankAccount >> #deposit:の定義は、
deposit: aNumber
self balance: self balance + aNumber
でしたね。今回の“騒ぎ”は、ここで送られる「+ aNumber」がnilに送られたためであることが総合的に判断できると思います。ではなぜ、nilに送られてしまったのか。答えは簡単で、self balanceがnilを返したからです。アクセッサ「#balance」の定義は、
balance
^ balance
ですから、同名のインスタンス変数「balance」に束縛されているオブジェクトを返すだけで、他にはなにもしていません。つまり、balanceにnilが束縛されているのが“騒ぎ”の真相ということになります。もう、すでに忘れておられる方もあるかもしれませんが第15回で触れたとおり、Smalltalkでは、未定義の変数には未定義値(undefined object)、すなわちクラスUndefinedObjectの唯一のインスタンスであるnilが束縛される決まりになっています。そして、nilは「+ 100」というメッセージを知らないので、自らに「doesNotUnderstand: …」を送信し、ノーティファイアを起動した、というわけです。
このページを編集 (5775 bytes)
|
以下の 1 ページから参照されています。 |
This page has been visited 1129 times.