vieweditattachhistorytopchangessearchhelp

第35回


■SqueakではじめるSmalltalk入門   第35回


 本連載では、名前は知っていてもなかなか触れる機会のないSmalltalkについて、最近話題のSqueakシステムを使って紹介しています。引き続き、コレクションの抽象クラス「Collection」のプロトコルを覗きながら、コレクションがどんなメッセージを受け付けるオブジェクトなのか、その“正体”を探ってゆきましょう。

 前回、コレクションとブロックの絶妙なコンビネーションで実現されるenumerating(列挙)プロトコルを先に終えたので、その前に飛ばしてしまったarithmeticプロトコルに話を戻します。

 Collectionクラスを選択した状態のブラウザで、arithmeticプロトコルをクリックしてメソッド一覧を見ると、そこには見慣れた二項演算子(二項メッセージセレクタ)が並んでいます。このことは、コレクションの仲間が、まるで数値のように演算操作を受け付けることを意味します。実際に試すとこんな感じです。

#(3 9 8 1) * 4  " => #(12 36 32 4) "


 各要素に同じメッセージ(この場合「* 4」)を送ったときの返値を改めて各要素とする新しいコレクションが作られ、それが返値となっています。ただしこの場合、コレクションに送られたメッセージと同じメッセージを、要素が受け付けることが前提となっているので注意が必要です。たとえば次のようなコレクションの場合、第三の要素のシンボル「#eight」は、「* 4」というメッセージをうまく処理できないので例外があがります。

#(3 9 #eight 1) * 4  " => Error "


 引数には数値以外にも同じコレクションを与えることも可能です。ただし、レシーバと引数で要素数が一致している必要があります。

#(12 36 32 4) / #(3 9 8 1)  " => #(4 4 4 4) "
#(4 8 12) / (1 to: 3)       " => #(4 4 4) "


 さて、このように引数によって変わる振る舞いを定義したメソッドは、いったい、どんな記述になっているのでしょうか。ちょっと想像してみてください。C++やJavaのように引数の型指定と多重定義が可能なら、想定される引数の型の数だけ関数(メソッド)を多重定義すればよいわけですが、あいにくSmalltalkには、引数の型指定も多重定義もありません。ならば、引数の型を判断して条件分岐…というのが常套ですが、Smalltalkでは別の面白い方法を使っています。

 仮にここで、#(1 2 3)に「* 4」を送信した場合を想定します。このとき#(1 2 3)が起動するのはCollection >> #*メソッドで、その定義はこれです。

Collection >> * arg
   ^ arg adaptToCollection: self andSend: #*


 なんともあっさりしたものですね。引数に対してレシーバ(今は#(1 2 3)。selfに束縛)と、このメソッドを起動するのに用いたメッセージのセレクタ(#*)を引数にした「adaptToCollection: self andSend: #*」というメッセージを送信するひとつの式が記述されているだけです。これでは#(4 8 12)という結果の説明ができないので、このメッセージ送信によって起動する#adaptToCollection:andSend:というメソッドの定義を追ってみましょう。(記憶力がよく、かつ、目先の利く読者のかたの中には、前々回飛ばしたadaptingプロトコルを思い起こされる向きもあるかもしれませんね。でも、それはいったん忘れてください)。

 システム内の#adaptToCollection:andSend:の定義をブラウズするためには、ブラウザ中段の「implementors」ボタンをクリックしてポップアップするメニューから「adaptToCollection:andSend:」を選択します。すると、よりシンプルな形のブラウザが現われ、上のペインにCollection、Number、Point、Stringが列挙されます。これは、この四つのクラスに#adaptToCollection:andSend:が定義されていることを意味します。

 今は、#(1 2 3)に「* 4」というメッセージを送信したとの仮定ですので、コレクションは改めて、引数の「4」に「adaptToCollection: self andSend:#*」というメッセージを送ることになることに注意してください。結果的に、前述四つのメソッドのうち、Number >> #adaptToCollection:andSend:が起動します。このメソッドの定義は次のようなものです。

adaptToCollection: rcvr andSend: selector
    ^ rcvr collect: [:element | element perform: selector with: self]


 前回、扱った#collect:が登場します。rcvrには、かつてのレシーバである#(1 2 3)が束縛されています。これを念頭に、メソッド本体の式を書き直すとこうなります。

#(1 2 3) collect: [:element | element perform: #* with: 4]


 #perform:with:は第一引数に与えられたパラメータをセレクタに、第二引数に与えられたパラメータを引数にしたメッセージ送信をシミュレートするメソッドです。つまり同式は、再度改めて次のように書き直すことができます。

#(1 2 3) collect: [:element | element * 4]


 一連のメッセージカスケードにより、最初の「#(1 2 3) * 4」が、上の式のように置き換えられ、評価されたと考えても良さそうです。なるほど、これならば#(4 8 12)が返ってくるはずですね。では、引数が4ではなく、#(4 5 6)のようなコレクションならどうでしょう。

#(1 2 3) * #(4 5 6)


 上の「* 4」のときと同様に、「* #(4 5 6)」というメッセージ送信から派生的に生じるカスケードを追ってみてください。これは次回までの宿題にいたしましょう。なお、#with:collect:という、まだ紹介していないメソッドも登場しますが、応用や想像で補うことで解釈は可能なはずです。

このページを編集 (4697 bytes)


Congratulations! 以下の 1 ページから参照されています。

This page has been visited 867 times.