Ruby は Smalltalk-76 に似ている。by アラン・ケイ 番外編


id:sumim:20060615#p1 の続き。いろいろと調べているうちに Smalltalk-76 で見かけた、Ruby にも Smalltalk-80 にもない、ちょっとおもしろい機能、編。

▼心持ちインテリジェントな配列要素アクセス

#• は、通常はパラメータである整数の位置にある要素を取り出すメソッドですが、整数以外にも an Interval などの配列もパラメータにできます。

('abc' • (3 to: 1 by: `1)) copy   " => 'cba' "

ここで、` は、数値リテラルの前に付することで、負数を表現するための記号です。


Ruby でも、順番を変えることまではできないものの、同様に a Range を渡して部分配列を得ることは可能ですね。

#ruby
"abcdef"[2..4]           # => "cde"
[10,20,30,40,50][2..4]   # => [30, 40, 50]


ちなみに、Array >> #• の定義はこんな感じです。

Array >> • x
   [⇑x subscripts: self] primitive: 38


プリミティブ、つまり「原始メソッド」の 38(おそらくパラメータである x に an Integer を期待する…)が失敗すると、レシーバ自身(self)をパラメータとして付し、#subscripts: を x に起動させます。プリミティブの失敗を介してはいますが、後に Smalltalk-80 のクラスライブラリで多用される「ダブルディスパッチ」(レシーバだけでなく、パラメータとの組み合わせのパターンも加味して多態する手法)の片鱗を見てとることができ、興味深いです。


たとえば仮に x が a Float などであるがために失敗しているときは、asInteger してから、改めて #• を起動します。

Number >> subscripts: a
   [⇑a•self asInteger]


もし、配列なら a Substring という特殊なオブジェクトを生成します。

Array >> subscripts: x
   [⇑Substring new data: x map: self]


a Substring は data と map という2つの配列を持ち、• anInteger に対して、こんなふうに振る舞います。

Substring >> • x
   [⇑data•(map•x)]

▼スマートなメッセージパターン宣言

メッセージパターンは、Smalltalk のメソッド定義の冒頭に位置する文で、メッセージを送る際のテンプレートのようなものをユーザーに示すのと同時に、メソッド定義に必須のセレクタ(メソッド名)とパラメータ変数(必要ならば…)を宣言する役割を担います。


Smalltalk-76 では、このメッセージパターンに使用するパラメータ変数にインスタンス変数を指定することができて、メソッド起動時に、インスタンス変数へ該当するパラメータを直ちに代入することを指示できます。


たとえば、Smalltalk-80 以降では、次のように、パラメータを受け取る val を介して、インスタンス変数「instVar」への代入の手続きを記述するところを…

setInstVar: val
   instVar := val


val の代わりに instVar を直接指定することで、冒頭のメッセージパターンのみで済ませることができる、というわけです。

setInstVar: instVar

セレクタが % で終わるメソッドでは、パラメータとして与えられた式を評価せずに引き渡す

注意:少なくとも手元の環境では、この種のメソッド(ただし制御構造を除く…)を、ワークスペースなどから do it や print it で直接起動しようとすると、確実にブラウザやアップレットビューワを巻き込んでハングアップしてしまいます。動作を試す際は、メソッドの中で使用し、そのメソッドを介して間接的に起動するようにしたほうがよいようです。


論理演算メソッドには #and: と #and% があります。それぞれの定義はこうなっています。

and: x [self ? [^ x] ^ false]
and% x [self ? [^ x eval] ^ false]


どうやら、セレクタの最後に % を付したメソッドは、メッセージ送信時にパラメータとして添えられた式を、評価せずにそのまま受け取ることができるようです。ためしに、Object >> #foo% を定義して確認してみましょう。

Object understands: 'foo% x [^ x]'.
Object understands: 'foo: x [^ x]'.
Object understands: 'foo [^ self foo% 3 + 4]'
Object new foo: 3 + 4   " => 7 "
Object new foo          " => Object>>something "

“何か”が返って来たようですが、これはたんに、#printon: の実装がそう返すように定義されているだけのようです。その正体は…

Object new foo class   " => Context "

と、a Context であることが分かります。もくろみ通りなら、eval を送信して評価することで、結果の 7 が返るはず。試してみましょう。

Object new foo eval    " => 7 "

ビンゴ。


話の流れ的に、むりやりこじつければ、Ruby の「ブロック付きメソッド呼び出し」と発想が似ていなくもないですね。