Smalltalk-72 で遊ぶ


id:lethevert さんのコメント欄で id:squeaker さんのツッコミがあったのをきっかけに、また久々に Smalltalk-72 で遊んでみたので、メモ。 今回は、(今の)Smalltalk について知見を深めるためというより、純粋に“Smalltalk-72”という言語に興味をもって接したせいもあってか、メッセージングというたったひとつのシンプルなルールだけで、言語としての豊かな“表現力”を示してみせるパワーというか妙というかにひどく驚かされながら、けっこう楽しめました。おかげで、これまでは「こんなの Smalltalk じゃないやいっ! ワーン!!゚゚(´□`。)°゚。」的イメージ(^_^;)だったのが、今ではすっかり Smalltalk-72 の にわかファンになってしまいましたとさ。w


素人考えですが、ちょっと言語仕様のようなものをリファインすれば、30年を経た今でも“新しい言語”として十分通用しそうな、うちに秘められた勢いのようなものを強く感じます。

概要

Smalltalk-72 は、Smalltalk という名前の言語としては2つめ、アラン・ケイのアイデアであるメッセージング( C++ や Eiffel によって広められ Smalltalk すらもはや無縁ではいられない「クラス指向」が主流の今となっては言ってもむなしいだけですが…“本来”の「オブジェクト指向」)を拠り所とする言語としては最初の実装です。名前に示されるように 1972 年頃から開発が始まり、少なくとも 1978 年頃まで GUI 周りなどに改良が加えられながら使われていました。Smalltalk はその後、1976 年頃にまたイチから新しく作り直された、Smalltalk-76 をもとにしてしだいに現在のかたち(市販された実装としては Smalltalk-80 が最初)に落ち着いてゆくことになります。


Squeak 用の Smalltalk-72 エミュレータは、ダン・インガルス(アラン・ケイのアイデアに基づき、初期の Smalltalk の設計および実装を担当したスーパーパーソン)がアラン・ケイの 1999 年の誕生日に送ったプログラム。

http://wiki.squeak.org/squeak/989
http://web.archive.org/web/20090521072249/http://www.smalltalkconsulting.com/html/smalltalk72.html
http://web.archive.org/web/20090519220847/http://www.smalltalkconsulting.com/papers/tipsAndThoughts/OOPSLA1999Thursday.html


クラス定義やタートルグラフィックスなど、Smalltalk-72 の主要な機能が利用できます。また、GUI 面では、フォントのグリフを ALTO から持ってきているとか、えらい懲りようです。一方で、ファイルやウインドウ操作ができないとか、デバッガがないとか、なにかあるとすぐにノーティファイアを表示して止まってしまうバギーな挙動とか、それなりに不都合も散見されますが、同時期にカール・ヒューイットの ACTOR(アクター理論の実装)や MIT の Flavors(ミックスイン、多重継承を有するオブジェクトシステムを組み込んだ LISP マシン LISP)に強い影響を与えた Smalltalk-72 がどんな言語だったのかを直にさわって知るのには十分でしょう。


Smalltalk-72 マニュアル(PDF)も上のページのリンクから入手できます。

http://www.esug.org/data/HistoricalDocuments/Smalltalk72/Smalltalk72Manual.pdf

インストール

Smalltalk-72 エミュ自体は Squeak 2.6 向けに作られたものですが、後に Squeak 3.2 用でも動くようにダン・インガルス自身がパッチを書いてくれましたので、Squeak 3.2 の環境をを用意します。

http://ftp.squeak.org/3.2/


別途、Smalltalk72.sar(実体は ZIP アーカイブ)を入手し、次に列挙するファイルを取り出します。

http://tinyurl.com/awroe

  • ALLDEFS
  • Smalltalk-72-di.cs
  • ST-72TweaksFor3.2-di.cs
  • ST72Font10.sf2

これらをイメージファイル(Squeak3.2-4956.image)と同じ階層に置き、Squeak システムを起動します。

念のため、新しいデスクトップ(プロジェクト)を デスクトップメニュー → open... → morphic project などとして開いた後、enter してから、ファイルリスト(デスクトップメニュー → open... → file list)を開いて、Smalltalk-72-di.cs 、ST-72TweaksFor3.2-di.cs の順に File-in to New します。


あと、show(プリティプリント機能。後述)使用時、最後に 41 と表示される不都合を修正(pshow)、および、addto(クラスへの定義追加機構。同) で、追加したいコードの挿入場所がおかしいため、元の定義を壊してしまうのを修正した ALLDEFS を用意しましたので decompress して、適宜、オリジナルのものと置き換えてください。

http://squab.no-ip.com:8080/collab/uploads/61/ALLDEFS.gz


加えて、edit により起動するクラス定義エディタで、マウス操作がうまくゆかないのを修正したチェンジファイルも用意しました。こちらも必要なら入手してファイルインしてください。

http://squab.no-ip.com:8080/collab/uploads/61/St72Fix-sumim.cs.gz


まず、ワークスペースなどで、次の式を do it(cmd/alt + D)します。

ST72Context bootFrom: 'ALLDEFS'


専用のトランスクリプト( Welcome to Smalltalk-72 )が現われ、ALLDEFS を評価しながら読み込みます。それが終わると、トランスクリプトには簡単なインストラクションが表示されます。


Smalltalk-72 のウインドウ(?)は、画面左下、16 @ 514 extent: 480 @ 184 の位置に現われるので、この領域を確保できるように、Squeak の実行に用いている OS が Squeak システム向けに用意したウインドウのサイズを広げておきましょう。この矩形の位置の目星をつけるのには、次の式を評価するのもよいかもしれません。ちなみにこの直上の空間(0 @ 0 extent: 512 @ 512)はタートルグラフィックスが使います。

Display border: (16 @ 514 extent: 480 @ 184) width: 2


フラップタブが邪魔なら Navigator → Hide tabs で消しておくことができます。


以上でインストール作業は終了です。このタイミングで、イメージを「Smalltalk-72.image」などとして保存(デスクトップメニュー → save as...)しておくことをお薦めします。


起動

Smalltalk-72 の起動…、もとい、Smalltalk-72 のマニュアルに倣えば“暫定ダイナブックオペレーティングシステム(Interim Dynabook operating system)”を起動するには、前述トランスクリプトのインストラクションの、

	t USER

の行をクリックしてキャレットを置いて(あるいは選択して評価範囲を明示的にしてから)do it します。


画面、左下に矩形が現われ Welcome to SMALLTALK [May 5] と次の行に ALTO アイコン(プロンプト)が出れば、起動は成功です。ちなみにこの May 5 はいつの May 5 なのかは分かりませんが、ブートストラップ(ALLDEFS)自体は、1974 年のものをベースにしているようです。


基本操作

式を入力した後、キーボードの上矢印(カーソル)キーを押すと「!」が現われ、式が評価され、次の行に返値が(あれば)表示されます。


ctrl + E で別ウインドウを呼び出し、ctrl + D(done!)でそこから抜ける(ウインドウを閉じる)ことができます。


トップレベルで done!(ctrl + D)すると、Smalltalk-72 が終了して、SqueakGUI に制御が戻ります。

特殊な記号とその入力方法

スマイリー(顔アイコン)は、@ をタイプして入力できます( @ が必要なときは ctrl + F で表示できます)。スマイリーにはデフォルトでは、turtle のインスタンス(タートルグラフィックスのタートル)が代入されています。


ハンド(指差しアイコン)は、" をタイプして入力できます( " が必要なときは ctrl + O で表示できます)。ハンドは、LISP の '(QUOTE)のような働きをする関数オブジェクトを代入しているシンボルです。


アイボール(∢)は、% をタイプして入力できます( % が必要なときは ctrl + V で表示できます)。アイボールは、メッセージパターンの宣言時に用います。

if ... then 記号(⇒)は、? で入力できます。if ... then 記号は条件文を記述するときに用います。


? は、 ~ で入力できます。? は、オブジェクトのクラスを問い合わせる式で用います。


リターン記号(⇑)は、! で入力できます( ! が必要なときは ctrl + Q で表示できます)。


関数オブジェクト定義

パラメータとして与えられた数(じつはメッセージ)の階乗を求める関数(じつはオブジェクト)として factorial を定義してみましょう。

to factorial n (:n. !(n < 2 ? (1) n * factorial n - 1))


ここで to は RubyScheme における def や define のようなものです。ただ、構文やスペシャルフォームというわけではなく、自身が関数オブジェクトでそれへのメッセージ式になっています(もっとも、Smalltalk-72 の関数/クラス定義自体がすでに構文定義やマクロみたいなものなので、この但し書きは微妙ですが…。ちなみに to がレシーバで factorial 以降がメッセージ。もちろん今の Smalltalk と文法は異なります)。この to は、クラス定義にも使います(後述)。

続く、factorial が関数オブジェクト名、n はテンポラリ変数宣言です。factorial のあとにあるので引数の受け渡しを表現しているように見えますが、そうではありません。引数の受け渡しは、次の行の :n のように明示的にしてやる必要があります。: は、変数 n に fact に送られるメッセージの最初の要素を代入するための関数オブジェクトです。: はプリミティブですが擬似コードによる定義の概要を ALLDEFS の to : で見ることができます。(SqueakSmalltalk での実体は、ST72Context >> #fetchFrom:inContext: )

to : name

   %" ?(:"name nil ?(!name_caller message quotefetch)
            (!caller message quotefetch))

   Fetch the next thing in the message stream unevaluated
   and bind it to the name if one is there.

   %# ?(:"name nil ?(!name_caller message referencefetch)
            (!caller message referencefetch))

   Fetch the reference to next thing in the message stream
   and bind it to the name if one is there.

      (:"name nil ?(!name_ caller message evalfetch)
         !caller message evalfetch)

   Fetch the next thing in the message stream evaluated
   and bind it to the name if one is there.


条件分岐は「 真偽を返す式 ?(真のとき評価される式) 偽のとき評価される式 」と書きます。


この定義に基づき、factorial に 3 を送ると 6 が、10 を送ると 3628800 が返ります。factorial 10 - 1 は、factorial に 10 - 1 というメッセージを(評価してから)送ることを意味します。もちろん、10 - 1 も 10 に - 1 というメッセージを送る式になっています。オブジェクトへのメッセージ送信というセマンティックスは、今の Smalltalk より強固に貫かれているようです(そもそもメッセージングというアイデアが有効であることを実証するための実装という側面ものあるので、当り前っちゃあ当り前ですがw)。


今の Smalltalk で、#(a b c) at: 1 put: #A などとしたとき、つい JavaRuby 界隈の言い回しに引っ張られて“at:put: というメッセージを 1 と #A という引数を付けて送る”というような説明をしてしまいがちですが、エンティティでこそないものの、あくまでこの式で #(a b c) に送られる“メッセージ”は「 at: 1 put: #A 」なのだと解釈しなければいけないのだなぁ…との認識(変な義務感(?))をここで新たにしました。w


クラス定義

関数オブジェクトと同様に to を使います。isnew ではじまる条件式モドキの真のときの評価式の項にインスタンスの初期化手続きを書きます。クラス定義にはこの isnew 条件式モドキを必ず入れなければいけません。メソッドは、同様にアイボールへのメッセージ送信で記述するパターンマッチ条件式モドキとして書き連ねます。クラスメソッド(ここで init )もインスタンスメソッド(同、dinstv )も区別しません。クラスは(一部、振る舞いに制限はあるものの)いずれのメッセージも区別無く受け付けますので、インスタンスベース(プロトタイプベース)っぽい香りもします。なお、Smalltalk-72 マニュアルでは、テンポラリ変数宣言、インスタンス変数宣言、クラス変数宣言の区切りは | なのですが、エミュレータでは : を使う必要があるようです。

to myclass tempv : instv : classv (
	%init ?("classv _ 1)
	%dinstv ?(!instv * 2)
	isnew ?("tempv _ 2. "instv _ tempv + 1))

クラスのシンボル(ハンドへのメッセージ送信で記述)への代入時、インスタンス化が行なわれます。


クラス定義の閲覧

show にメッセージとして送信したクラスのコードのソースを表示します。Smalltalk-72 はソースをリスト(vector)で持っていて、都度、eval しているので、それをちょっと整形して出力しているだけです。ソースのロギングやデコンパイルのような複雑な手続きや機構が機能しているわけではないようです。


余談ですが、Squeak などの後の Smalltalk では、対話的環境ながらも、コンパイル済みメソッドから自身を生成するために用いられたソースをコメントなどの付加情報付きで容易にたぐれる仕組みが用意されています。

(Integer >> #factorial) getSourceFromFile
=> a Text for 'factorial
      "Answer the factorial of the receiver."

      self = 0 ifTrue: [^ 1].
      self > 0 ifTrue: [^ self * (self - 1) factorial].
      self error: ''Not valid for negative integers'''

もちろんデコンパイルしてソースを得ることも可能です

(Integer >> #factorial) decompileString
=> 'factorial
      self = 0
         ifTrue: [^ 1].
      self > 0
         ifTrue: [^ self * (self - 1) factorial].
      self error: ''Not valid for negative integers'''

既存クラスへのメソッド追加

addto 関数オブジェクトを使います。

addto myclass "(%tinstv ?(!instv * 3))


これを使えば数値に fact を送信して答を得る階乗も定義できそうです。

addto number "(%fact ?(!(SELF < 2 ?(1) SELF * (SELF - 1) fact)))


ただ、addto は、定義を追加するだけで、既存の同名メソッドの定義があっても、それを重ね書きして書き換えてくれるわけではありません。

addto myclass "(%dinstv ?(!instv * 10))


パターンマッチは定義順に行なわれるので、重複した定義は無視されます(この例では、上のメソッドを追加したあとも、相変わらずインスタンス変数の倍の数値を返してきます)。したがって、新しく追加した重複定義を活かすには、次の定義の編集機能を使って古い定義を削除するか、最初から、この機能を使って既存の定義を書き換えてやる必要があります。

クラス定義の編集

edit にクラス名(あるいは関数オブジェクト名)を送信すると編集 GUI が起動します。原則として右手のメニュー項目をクリックして選択(反転)した後、コードの操作対象部分をクリックして選択します。選択して操作…という、後の Smalltalk で採用されたオペレーションスタイルはこの時点ではまだ確立されていなかったのですね。

vector は省略記号 () で表示されるので、内容を見たり編集するにはそこへ Enter します。抜けるには Leave です。

削除は Delete 選択後、始点と終点の順にクリックして選択します。


他に、定義の追加は Add(最後へ)、Insert(任意の位置へ)、Replace(任意の範囲を置き換えて)を使います。Add 以外は位置や範囲を指定後、プロンプト( ALTO アイコン)が現われるので、挿入したいコードを入力して do it!(上矢印キー、!)します。

Up はクリックして指定した () の括弧の取り外し、Push は指定した範囲を括弧で括る作業を行ないます。

トップレベルでの Leave もしくは任意のレベルでの Exit で変更を保存して終了します。


元の定義を削除したあとでは、dinstv の結果も新しい定義に従います。

汎用アクセッサの定義と追加

's 記号(メッセージ)を次のように追加定義する(あるいはあらかじめ定義に含めておく)ことで、インスタンス変数やクラス変数にアクセスする際に便利な汎用アクセッサとして用いることが可能です。(['s] は ctrl + S などで、オープンコロン [:] は ; などで直接入力する必要があるので、このコードに限って、コピペでの入力はできません)

addto myclass "(%['s] ?("v _ [:]. %_ ?(v _ :.) !v eval))


この定義は、

  1. メッセージに 's を見つけると、とりあえず 's に続くアトムをテンポラリ変数 v に代入。
  2. 's に送られるメッセージに ←(_)があれば、さらに次のアトムを評価して v に代入してある変数名の変数に代入。
  3. そうでなければ、v に代入してある変数名を eval して結果をリターン。

という仕組みになっています。オープンコロンは、メッセージ中の次のパラメータを取り込む点で : と似ていますが、式を評価せずにアトム、あるいは、リスト(vector)のまま取り込む点で異なります。



長くなりましたが、とりあえずこんなところで。タートルのサンプルなんかは、比較的お手軽に遊べますので Smalltalk-72 マニュアルを参照してみてください。


追記
タートルグラフィックスについては、一年ほど前に id:propella さんこと山宮さんが比較的詳しくまとめておられます。有名な「 Joe との対話」などへの言及もあるので、こちらもぜひ。

http://web.archive.org/web/20150102043018/http://www.languagegame.org:8080/propella/74