第36回
■
■SqueakではじめるSmalltalk入門 第36回
■
本連載では、名前は知っていてもなかなか触れる機会のないSmalltalkについて、最近話題のSqueakシステムを使って紹介しています。コレクションの抽象クラス「Collection」のプロトコルを覗きながら、コレクションがどんなメッセージを受け付けるオブジェクトなのか、その“正体”を探っているところなのですが、ちょっと寄り道して、コレクションに対する二項演算がどのように実現されているのかを見ています。
前回は、数値を引数にとる場合の解説をしました。たとえば#(1 2 3)に「* 4」というメッセージを送信している式なら、Collection >> #*から、Number >> #adaptToCollection:andSend:が呼び出され、そこでの定義から元の式が、
#(1 2 3) collect: [:element | element * 4]
と解釈可能となり、結果、各要素に「* 4」を送信して得られた結果を要素とする新しいコレクション#(4 8 12)が返ってくる…という流れでした。
そして課題は、パラメータが4ではなく#(4 5 6)のとき、どんなカスケードが起こるのか…、もう少しうがった事を言うと、case-switchのような条件分岐を使わずにパラメータの違いによる振る舞いを変更するためにどうしているのか?でしたね。では、早速。
とりあえず#(1 2 3)は、送られるメッセージが「* 4」であろうと「* #(4 5 6)」であろうと、セレクタが「#*」である限り、同じメソッドCollection >>#*を起動します。
Collection >> * arg
^ arg adaptToCollection: self andSend: #*
まったく同じメソッドの起動ですから、この時点ではまだ多態はしていません。
前回、引数argには、NumberのサブクラスであるSmallIntegerに属する4が束縛されていましたが、今回はCollectionのサブクラスであるArrayに属する#(45 6)を束縛しています。argに送るメッセージは同じですが、argに束縛されているオブジェクトが異なるので、起動するメソッドも、4のときのNumber >>#adaptToCollection:andSend:とは別のものになります。
ここでようやく振る舞いに変化が生じます。引数argに改めてメッセージを送っているのがミソですね。なお、このようにレシーバだけではなく、引数についてもこれに依存的な多態を実現するための機構を、一般に「ダブルディスパッチ」と呼びます。
Arrayとそのスーパークラス群にはCollectionに至るまで#adaptToCollection:andSend:は定義されていないので、結局、argに束縛された#(4 5 6)は、Collection >> #adaptToCollection:andSend:を起動します。
Collection >> adaptToCollection: rcvr andSend: selector
rcvr isSequenceable & self isSequenceable ifFalse:
[self error: 'Only sequenceable collections may be combined arithmetically'].
^ rcvr with: self collect:
[:rcvrElement :myElement |
rcvrElement perform: selector with: myElement]
最初の式は、レシーバ、引数のいずれかが要素の順番を扱うことができないコレクションならエラーを生じさせるためのフェールセーフです。とりあえずこれは無視して第二式目に集中しましょう。「* 4」のときと同様に、self、rcvr、selector、それぞれに実際に束縛されているオブジェクトを割り当てて、より分かりやすい式に書き換えてみると次のようになります。ブロック変数名も短くしてみました。
#(1 2 3) with: #(4 5 6) collect: [:a :b | a perform: #* with: b]
さらに、#perform:with:も、第一引数のセレクタが確定していれば、等価なメッセージ式に置き換えることができましたよね。結果、#(1 2 3) * #(4 5 6)は、次のように解釈されると考えてよさそうです。
#(1 2 3) with: #(4 5 6) collect: [:a :b | a * b] " => #(4 10 18) "
#with:collect:は初出ですが、#collect:と見た目が似ているので動作の予想は容易いと思います。ブロックを評価した結果を集めて(collect=コレクト…して)返す点では同じです。ただ、#collect:はレシーバの各要素を評価に用いるのに対し、#with:collect:は第二引数のコレクションの同じ順番の要素も一緒に用います。詳しくは#with:collect:の定義をご覧ください。#with:collect:の定義は、ブラウザ中段のimplementorsボタンを押したときに現われるポップアップから「with:collect:」を選んで呼び出すのが早いでしょう。
さて。前回から引き続き、以上駆け足でしたが、#(1 2 3)に「* #(4 5 6)」というメッセージを送信したとき、対応する各要素の積を要素とする配列#(410 18)が返ってくるしくみ、ひいては「* 4」を送信したときと振る舞いを変えることがどうして可能なのかについての理解を深めていただくことができたかと思います。case-swithという手続き的な記述で済ますのではなく、あくまでオブジェクトにメッセージを送って、その適切な振る舞いに期待する…というスタイルは、まさに(ケイの)「オブジェクト指向」の面目躍如といったところでしょうか。また、実際にシステム内で運用されている、こうしたカラクリのかなりの部分がSmalltalk自身で記述されており、いつでも気軽に簡単にそのソースに当たることができる…という点も、Smalltalkシステムならではの特徴と言えそうです。
このページを編集 (4458 bytes)
|
以下の 1 ページから参照されています。 |
This page has been visited 829 times.