Matzにっき - Alan Kayといっしょ によれば、前のエントリーで取り上げさせていただいた“死んだ言語”以外にも、タイトルのような示唆に富む言及があったとのことだったので、さっそく調べてみました。正直、これまで Smalltak-76 はあまり興味がなかったのですが、Ruby は(Smalltalk-80 より)76 に似ていると御大がのたまうのを耳(目?)にしたからには、話は別です(^_^;)。
関連:
残念ながら Smalltalk-76 のオリジナルの処理系は、当然、ALTO とともに失われてしまっているので、ダン・インガルスの The Smalltalk-76 programming system design and implementation(PDF)が主な情報源ということになります。しかし、幸いなことにインガルス協力のもと、ヘルゲ・ホルヒにより Java で実装された処理系が存在するので、これで文書から得ることができない情報をある程度は補うことはできそうです。
- Bits Of History(Smalltalk-76 Runtime Engine via web.archive.org)
- Let me try with my Browser's JavaPlugin... のリンクから起動できます。
手元の環境では OS X の Safari 2.0.3、Win XP の Firefox 1.5.0.4 を介して動作することを確認しています。
Smalltalk 環境に慣れている人は、操作に際して特に問題はないと思います。慣れていない人へのアドバイスとして…。
当時の Smalltalk を OS として動作する ALTO、つまり「暫定ダイナブック環境」は、Mac や Win などの GUI における、現在主流のルック&フィールの手本になったものなので、ウインドウやテキスト編集操作に関しては(若干のアレンジはあっても、本質的には)ほぼ同じと考えてよいのですが、メニュー操作だけは根本的に異なるので注意が必要です。というのも、Apple でワンボタンマウス操作のために後に発明されることになる「プルダウンメニュー」やそれを収める「メニューバー」という機構が、まだ、なかったからです。
そこで、暫定ダイナブック環境では、注目あるいは選択した対象に対する操作はすべて、黄ボタンクリック(第2ボタンクリック。現在の環境では、多くの場合、右ボタンクリックに相当)でメニューをポップアップさせて操作するスタイルをとります。最低限、これだけ頭に入れておけば、この Smalltalk-76 環境を含め、Smalltalk 環境(特に Squeak システムのような古典的 Smalltalk システム)で遊ぶのに、とっかかりがまったくない…ということはなくなるでしょう。
では、この実装を通じて、Ruby に似た印象を持つところを中心に、Smalltalk-76 の特徴をざっと確認してみます。
▼メタクラスがない
Smalltalk-76 と Smalltalk-80 以降(つまり、Squeak システムを含めた現在の Smalltalk )との大きな違いのひとつは、継承ツリーにメタクラス階層がないことです。デスクトップの右クリックでポップアップするメニューから「Open Browser」を選んで、システムブラウザを開いても、お馴染みの「instance」「class」のモード切替ボタンが見あたらないのはこのためです。
念のためメタクラスというのは、Smalltalk において、オブジェクトとしてのクラスが属するクラスのことです。たいていは無名で「クラス名 class」と呼称されます(余談ですが、これは Smalltalk の式としても評価可能なので、メタクラスの実体を参照するための手段のひとつとしても使えます)。主にクラスメソッド(クラスに直接メッセージを送って起動するメソッド)を定義するのに用いられ、継承ツリーはクラスのそれに準じます。
"Smalltalk-80 以降" Array class " => Array class " Array superclass " => ArrayedCollection " Array class superclass " => ArrayedCollection class "
ちなみに、メタクラスは、クラス「Metaclass」のインスタンス。当然「Metaclass class」もクラス「Metaclass」のインスンタス。つまり、「Metaclass」と「Metaclass class」は互いに互いのクラスであり、インスタンスでもあるという不思議。
"Smalltalk-80 以降" Array class class " => Metaclass " Array class class class " => Metaclass class " Metaclass class " => Metaclass class " Metaclass class class " => Metaclass "
ところで、メタクラスは無名なのでシステムブラウザのクラス名リストには現れません。システムブラウザで、その定義をブラウズする必要があるときは、クラス名リスト枠内の同名(?)のクラス(正確には、そのメタクラスのインスタンスであるクラス)を選択した状態で、同じクラス名リスト枠にある「class」ボタンでモードを切り替えます。
メタクラス階層は、Smalltalk の初学者を混乱させる要因のひとつとして、よく挙げられます。(すでに、上の例を見れば明らかでしょう…w) アラン・ケイも The Early History of Smalltalk の Smalltalk-80 の節で、ピーター・ドイチュの言葉を引用し、次のような批判を加えています。(ちなみに個人的にはメタクラス階層は Smalltalk システムで好きなところの一つなのですが…。そんなことは聞いてませんか、そうですか(^_^;))。
The most puzzling strange idea--at least to me as a new outsider--was the introduction of metaclasses (really just to make instance initialization a little easier--a very minor improvement over what Smalltalk-76 did quite reasonably already). Peter's 1989 comment is typical and true: "metaclasses have proven confusing to many users, and perhaps in the balance more confusing than valuable."
Smalltalk-76 や Ruby では、クラスはクラス「Class」に属するインスタンスでこそありますが、メタクラス階層のような二重構造状のものは持っておらず、継承ツリーはいたってシンプルです。
"Smalltalk-76" Array class " => Class " Class class " => Class "
#ruby Array.clsss #=> Class Class.class #=> Class
では、Ruby でクラスメソッドを必要とするときどうするかというと、「特異クラス」と名付けられた、インスタンス特異的な無名のクラス(通常の方法では参照できない)をアドホックに作り、そこにメソッドを定義する方法をとっています。なお、こうして定義されたインスタンス特異的メソッドを「特異メソッド」と呼びます。換言して、Ruby のクラスメソッドはクラスの特異メソッドとして実現されている…とも説明されます。
>> Array.class #クラス Array の属するクラスは… => Class >> Array.class.instance_methods(false) #そこに定義されたメソッドは… => ["new", "superclass", "allocate"] >> def Array.one_two_three; [1,2,3] end #Array にクラスメソッドを定義。 => nil >> Array.one_two_three #念のため、動作確認… => [1, 2, 3] >> Array.class.instance_methods(false) #しかし Class には追加された形跡がない。 => ["new", "superclass", "allocate"] >> def singleton_class; class << self; self end end #特異クラスを参照するためのメソッドを定義… => nil >> Array.singleton_class #出でよ! 特異クラスよ! => #<Class:Array> >> Array.singleton_class.class #特異クラスのクラスも Class 。 => Class >> Array.singleton_class.instance_methods(false) #クラスメソッドはここにありました。 => ["[]", "new", "superclass", "allocate", "one_two_three"] >> Array.singleton_methods #特異メソッドの一覧は、普通はこう。 => ["[]", "one_two_three"]
Smalltalk-76 には Ruby の特異クラスのような機構はないので(あったら ビックリですw)、クラスがメッセージ送信により起動できるメソッドは、それが属するクラスである Class に定義されたメソッドだけです。
これに関しては、起動時に現われる Welcome! ウインドウにあるサンプルで Pen や Host など、ぱっと見ではクラスを指すことを連想させる、大文字で始まる変数に対してメッセージが送られていることに気づかれたかたもおられるかも。こうした式は、まるでクラスメソッドを起動しているかのように思えます。
しかし実際には違って、これら大文字で始まる変数にはたんに、あらかじめ、しかるべきクラスのインスタンスが(たとえば、Pen、Host なら、それぞれ、a Turtle、a HostReflector が)代入されていて、それにメッセージを送っている(当然、起動するのは、おのおのが属するクラスに定義されたインスタンスメソッド…)にすぎません。それぞれを選択して print it(ctrl + p)してみれば、その実体がなんだかはすぐに分かります。
jar を入手して source ディレクトリにあるブートストラップファイル(bootstrap.utf.txt)内の、Pen の定義を見てみるのもよいでしょう。なお、Pen の前にある指さしアイコンは文字化けではなくて(いや、ブラウザによっては本当に化けている場合もありますが…(^_^;))、LISP の QUOTE(')みたいな働きをする記号で、Smalltalk-72 のころから使われていたものです(参考:id:sumim:20060121:p1)。
Smalltalk define: ☞Pen as: Turtle new init
▼セレクタに ←(Ruby でいうところの = )を含めることができる
今の Smalltalk では、代入は、:= か _(後者は Squeak システムだけ。表示上は ← というグリフが使われる)でメッセージではなく(関連:Smalltalk のテンポラリ変数への代入をメッセージングで処理してみる試み)、セレクタとしてもこれらを用いることはできません。このため、たとえば配列の要素の置き換えなどの式には、#at:put: という説明的なセレクタを用いています。
"Smalltalk-80 以降" | array | array := #(1 2 3 4). array at: 1 put: #10. array
=> #(10 2 3 4)
しかし、Smalltalk-76 では ← をセレクタに含めることができるようで、次のように変数への代入と一貫性をもった直感的な表現が可能になっています。
"Smalltalk-76" array ← (1, 2, 3, 4). array • 1 ← 10. array
=> (10, 2, 3, 4)
念のため Array >> #•← (Smalltalk-80 以降の #at:put: )の定義はこう。
• x ← val [⇑x subscripts: self ← val] primitive: 39
ここで使用されている #subscripts:← も同様で、セレクタに ← を含んでいます。
Ruby では、メソッド Array#[]= が定義されており、次のように運用されます。
#ruby ary = [1,2,3,4] ary[0] = 10 ary
=> [10,2,3,4]
ただ、Ruby の場合、(再)定義の際にメッセージパターン(そのメソッドを起動するために送信するメッセージのテンプレートのようなもの)を宣言するという概念がないので、def 文では、
def []=(idx, val) #... end
のように通常の言語における関数定義を模した方法で記述する必要があります。
Ruby では、この種のメソッド名の場合、[、]、= の任意の位置にスペースを挿入できますが、Smalltalk-76 でも、← で終わるメソッドの場合(#•← のように ← の前にパラメータがある場合はもちろん、ない場合でも)← の前にスペースを挿入できます。この点でも両者は似かよっていると言えそうです。Smalltalk-80 のセレクタには、このような柔軟性はありません。
"Smalltalk-76" array ← (1, 2, 3, 4). array last ← 10. " セレクタは #last← " array
=> (1, 2, 3, 10)
余談ですが、上の Smalltalk-76 における配列の記述はリテラル式のように見えて、じつはリテラル式ではありません。第一要素となるオブジェクトへの「, val」というメッセージ送信により、レシーバと val からなる配列を返すメソッド「Object >> #,」を起動しています。また、配列への同様のメッセージ送信は val を追加した配列を返すメソッド「Vector >> #,」を起動するので、結果、#, で区切って列挙した配列が返される…というカラクリになっています。
なお、現在の Smalltalk で #, は配列同士を連結する振る舞いに割り当てられているので、このような記述はできません。連結といえば、文字列については、Smalltalk-76 も Ruby 同様、#+ を使います。
"Smalltalk-76" 'abc' + 'def'
=> 'abcdef'
#ruby 'abc' + 'def'
=> 'abcdef'
配列は違うようです。
"Smalltalk-76" (1, 2, 3) concat: (4, 5, 6)
=> (1, 2, 3, 4, 5, 6)
いや。たんに String >> #+ のように、Array >> #concat: のエイリアスとして Array >> #+ が定義されていないだけ…という話なだけかもしれません(^_^;)。
▼ブロックがオブジェクトじゃない
これは事実ですが、下のサンプルはネタです(^_^;)。あしからず。(関連: Ruby のブロックってオブジェクトじゃないよね。これって“驚き最小の法則”に反しない?)
"Smalltalk-80 以降" [3 + 4] class
=> BlockContext " あるいは BlockClosure "
"Smalltalk-76" [3 + 4] class
=> Integer " 遅延評価が必要でない文脈では中味は評価されてしまうらしい… "
#ruby {3 + 4}.class
=> SyntaxError: compile error # いや、だから、ネタですってば…。
と、まあ、以上。Ruby と Smalltalk-76 が(Smalltalk-80 より)似ていそうなところを、たった三点を挙げられただけ(しかも、本質的に似かよったものだというわけでもなく…)ですが、思いのほか長くなってしまったので、とりあえずこのへんで。
id:sumim:20060615#p1 に続く。