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


id:sumim:20060613#p1 の続き。Smalltalk-76 の勉強を兼ねて、Smalltalk-76 が Ruby に似ているっぽいところを列挙しています。

▼制御構造の書き方

Smalltalk-80 以降では、条件分岐や繰り返しなどの制御構造もすべて、しかるべきレシーバに対するメッセージ送信として記述します(少なくとも見た目は…。実際はコンパイル時にインライン展開されて、通常の制御構造文として解釈、実行)。しかし、Smalltalk-76 では Ruby 同様、メッセージ式ではない、専用の構文ライクな特別な式を用います。

条件分岐
"Smalltalk-80 以降では、通常のメッセージ式で"
3 < 4 ifTrue: [5] ifFalse: [6]
"Smalltalk-76"
if% 3 < 4 then% [5] else% [6]
3 < 4 ? [5] 6
#ruby
if 3 < 4 then 5 else 6 end
3 < 4 ? 5 : 6
繰り返し: パラメータを 1(もしくは 0)から 1 ずつ増やしながら指定回数
"Smalltalk-80 以降にはないので、配列や数値へのメッセージ送信で代用"
(1 to: 10) do: [:i | Transcript cr; show: i]   " 1 から 10 までを出力。配列へ送信 "
1 to: 10 do: [:i | Transcript cr; show: i]     " 1 へ送信するバージョン "
"Smalltalk-76"
for% i to: 10 do% [user show: i; cr]           " 1 から 10 までを出力 "
#ruby
for i in 0...10 do p i end                     # 0 から 9 までを出力
10 times {|i| p i}                             # ブロック付きメソッドで
繰り返し: パラメータを指定した配列の要素で
"Smalltalk-80 以降では配列への、ブロックを引数として添えたメッセージ送信で"
#('a' 'b' 'c') do: [:str | Transcript cr; show: str]     " 'a'、'b'、'c' を表示 "
"Smalltalk-76"
for% str from: ('a', 'b', 'c') do% [user show: str; cr]  " 'a'、'b'、'c' を表示 "
#ruby
for str in ["a", "b", "c"] do p str end                  # "a", "b", "c" を表示
["a", "b", "c"].each {|str| p str}                       # ブロック付きメソッドでも
繰り返し: 条件付き繰り返し
"Smalltalk-80 以降ではブロックに対するメッセージ送信で"
| n | n := 0.
[(n := n + 1) < 10] whileTrue: [Transcript cr; show: n].
[(n := n - 1) = 0] whileFalse: [Transcript cr; show: n]
"Smalltalk-76"
n ← 0.
while% (n ← n + 1) < 10 do% [user show: n; cr].
until% (n ← n - 1) = 0 do% [user show: n; cr]
#ruby
n = 0
while (n += 1) < 10 do p n end
until (n -= 1) == 0 do p n end

▼文字オブジェクトがない

"Smalltalk-80 以降"
'abc' first         " => $a "
'abc' first class   " => Character "
"Smalltalk-76"
'abc' • 1           " => 97 "
('abc' • 1) class   " => Integer "
#ruby
"abc"[0]            # => 97
"abc"[0].class      # => Fixnum


id:sumim:20060615#p2 に続く。

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 の「ブロック付きメソッド呼び出し」と発想が似ていなくもないですね。