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


Matzにっき - Alan Kayといっしょ によれば、前のエントリーで取り上げさせていただいた“死んだ言語”以外にも、タイトルのような示唆に富む言及があったとのことだったので、さっそく調べてみました。正直、これまで Smalltak-76 はあまり興味がなかったのですが、Ruby は(Smalltalk-80 より)76 に似ていると御大がのたまうのを耳(目?)にしたからには、話は別です(^_^;)。


関連:


残念ながら Smalltalk-76 のオリジナルの処理系は、当然、ALTO とともに失われてしまっているので、ダン・インガルスの The Smalltalk-76 programming system design and implementationPDF)が主な情報源ということになります。しかし、幸いなことにインガルス協力のもと、ヘルゲ・ホルヒにより Java で実装された処理系が存在するので、これで文書から得ることができない情報をある程度は補うことはできそうです。

  • Bits Of HistorySmalltalk-76 Runtime Engine via web.archive.org)
  • Let me try with my Browser's JavaPlugin... のリンクから起動できます。



手元の環境では OS XSafari 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 SmalltalkSmalltalk-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    # いや、だから、ネタですってば…。

と、まあ、以上。RubySmalltalk-76 が(Smalltalk-80 より)似ていそうなところを、たった三点を挙げられただけ(しかも、本質的に似かよったものだというわけでもなく…)ですが、思いのほか長くなってしまったので、とりあえずこのへんで。


id:sumim:20060615#p1 に続く。

LISP 誕生から Smalltalk 誕生までは何年?


某所で見かけたのですが、ちょっと計算…というか、起点のとりかたが一貫していないように思えたので詷べてみました。


History of LISP [McCarthy78] の冒頭に、こんなふうな記述があります。

1. Introduction.

This paper concentrates on the development of the basic ideas and distinguishes two periods - Summer 1956 through Summer 1958 when most of the key ideas were developed (some of which were implemented in the FORTRAN based FLPL), and Fall 1958 through 1962 when the programming language was implemented and applied to problems of artificial intelligence. After 1962, the development of LISP became multi-stranded, and different ideas were pursued in different places.


LISP の前身である FLPL(Fortran List Processing Language)でマッカーシーのアイデア[MacCarthy58] が試されたのが 1958 年の夏頃。このころの LISP と似たような状態(car や cdr が実装されたばかり)が Smalltalk ではどうかというと、ちょうどメッセージングで 3 + 4 が実行できるかどうかが検証されたころ…ということになりましょうか。該当する記述は The Early History of Smalltalk では、こんなふうにあります。ちょっと長めですが、引用してみましょう。

It turned out to be more difficult than I had first thought for three reasons. First, I wanted the program to be more like McCarthy's second non-recursive interpreter--the one implemented as a loop that tried to resemble the original 709 implementation of Steve Russell as much as possible. It was more "real". Second, the intertwining of the "parsing" with message receipt--the evaluation of paremters which was handled separately in LISP--required that my object-oriented interpreter re-enter itself "sooner" (in fact, much sooner) than LISP required. And, finally, I was still not clear how send and receive should work with each other.

The first few versions had flaws that were soundly criticized by the group. But by morning 8 or so, a version appeared that seemed to work (see Appendix III for a sketch of how the interpreter was designed). The major differences from the official Smalltalk-72 of a little bit later were that in the first version symbols were byte-coded and the reeiving of return of return-values from a send was symmetric--that is, reciept could be like parameter binding--this was particular useful for the return of multiple values. for various reasons, this was abandoned in favor of a more expression-oriented functional return style.

Of course, I had gone to considerable pains to avoid doing any "real work" for the bet, but I felt I had proved my point. This had been an interesting holiday from our official "iconic programming" pursuits, and I thought that would be the end of it. Much to my surprise, only a few days later, Dan Ingalls shoed me the scheme working on the NOVA. He had coded it up (in BASIC!), added a lot of details, such as a token scanner, a list maker, etc., and there it was--running. As he liked to say: "You just do it and it's done."

It evaluted 3+4 v e r y s l o w l y (it was "glacial", as Butler liked to say) but the answer always came out 7. Well, there was nothing to do but keep going. Dan loved to bootstrap on a system that "always ran," and over the next ten years he made at least 80 major releases of various flavors of Smalltalk.


これは 1972 年の秋(関連:id:sumim:20050225#c)。ですから LISP 誕生から Smalltalk 誕生までは 14 年から 15 年とするのが順当でしょうね。ぜんぜん関係ないですが、Ruby の誕生日は 1993 年 2 月 24 日だそうですから、LISP からは約 35 年、Smalltalk からは切りのいいところで約 20 年とするのが妥当なようです。なんにせよ、Smalltalk-80 が 1981 年に Byte 誌に発表された時点にはもう、すでに“死んだ言語”だったわけですから(超新星みたいやね…w)、これを起点にする手はありません。



ときに、先のマッカーシーの文献は、いろいろと面白いことが書いてあります。最初の LISP には car、cdr のほかに cpr、ctr、それに cwr なんてのもあったようです。それぞれ、address、decrement、prefix、tag というパート、そしてそれらが収められた word、各々の内容を参照するための関数でした。

The first problem was how to do list structure in the IBM 704. This computer has a 36 bit word, and two 15 bit parts, called the address and decrement, were distinguished by special instructions for moving their contents to and from the 15 bit index registers. The address of the machine was 15 bits, so it was clear that list structure should use 15 bit pointers. Therefore, it was natural to consider the word as divided into 4 parts, the address part, the decrement part, the prefix part and the tag part. The last two were three bits each and separated from each other by the decrement so that they could not be easily combined into a single six bit pair.