前のエントリーで まつもとさんにコメントをいただいたのをきっかけにして、Little Smalltalk で遊んでみたので、そのメモ。
Little Smalltalk はティモシー・バッドによる、名前のとおり非常に小さな Smalltalk 処理系です。Smalltalk システムの特徴であり、そのとっつきにくさの主犯(?)格でもある、GUI ベースの OS 的性質(もともと暫定ダイナブック環境であった)を徹底的に排除し、純粋な言語処理系としてのエッセンス抽出を意識して作られています(たぶん)。
よく知られている GNU Smalltalk などと同様、本家 XEROX Smalltalk の系譜(VisualWorks や Squeak)とは関係ないファンお手製の我流 Smalltalk 処理系の一つです。加えて、GNU Smalltalk が ANSI 準拠であるのに対し、Little Smalltalk は、Smalltalk としてはオリジナリティに富んだ実装のひとつであることにも(これで Smalltalk を学ぶ際には)留意する必要があります。
まあ、ときとして標準化が“死”をも意味することがある Smalltalk としては、どちらかといえば、オレ流 Little Smalltalk のほうがあるべき姿…とも言えなくもないのですが…(それを言ったら、ANSI 準拠いかんを問わず、既存の Smalltalk 処理系は皆、程度の差こそあれ、それぞれにオレ流を貫いていますしね)。
ティモシー・バッドは、この処理系を解説した本も著していて、邦訳本も出版されました。両著とも残念ながら現在、少なくともアマゾンには在庫はないようです(絶版?)。
ただ、改訂版で加筆された情報を中心とした PostScript ファイルが入手可能。
原著の PDF を見つけました。
比較的コンパクト(50 ページ強)にまとめられたリファレンスマニュアルも公開されています。
余談ですが、この Little Smalltalk 処理系には Java VM で動作するバージョンも。 思いっきり GUI に日和っているのはご愛敬?
ビルドと起動
必要なファイルは次の場所から入手できます。
http://web.engr.oregonstate.edu/~budd/Books/little/info/small.v3.tar
make にはいくつかのオプションがあるようです。
$ make select one of the following to make bsdtty: Berkeley 4.2/4.3 with teletype interface sysvtty: System V with teletype interface bsdx11: Berkeley 4.2/4.3 with stdwin interface on top of X11 ibmturboc: IBM PC with Turbo C compiler (see install.ms)
よくわからないので(ぉぃ…)今回は make bsdtty を使いました。あらかじめ PATH を通しておく必要があるようです。いくつか warning が出ますが、どんな感じか試すだけなら問題なさそうなので放っておきます(^_^;)。
PATH が通ったままなら st で起動できます。そうでなければ ./st などで。
$ st Little Smalltalk, Version 3.04 Written by Tim Budd, Oregon State University object count 2989 >
お約束の 3 + 4 で 7 が返ってくればとりあえずOK。
> 3 + 4 7 object count 2993 >
クラスの定義
せっかくなので、前のエントリーのインスタンス変数の多重定義の例を試してみましょう。まずはクラス Super から。
Object addSubClass: #Super instanceVariableNames: 'instVar'
それへのメッセージ送信で、サブクラス Sub も作ります。多重定義の実験のため、わざと(Super から継承されるので本来は不要な)同名のインスタンス変数を含めておきます。
Super addSubClass: #Sub instanceVariableNames: 'instVar'
VisualWorks や Squeak のときと違い、インスタンス変数名に重複があっても特に警告は発しないようです。
アクセッサの定義(Super >> #instVar、#instVar:)
Super addMethod
で、デフォルトのエディタが起動します。そこでメソッドの定義を保存して終了すると、Little Smalltalk に戻ってきて、コンパイルされたメソッドがレシーバであるクラス(この場合 Super )に追加されます。代入の記号には、_ や := ではなく <- を使うようです。
instVar ^ instVar
instVar: anObject instVar <- anObject
メソッドが定義されているかを念のため確認するには、次のメッセージ式を評価します。
Super methods
=> Dictionary ( instVar instVar: )
値の代入の様子を確認
インスペクタのような便利なツールはないのですが、#basicSize、#basicAt:、#basicAt:put: で似たようなことはできそうです。
(1 to: sub basicSize) asArray collect: [:i | sub basicAt: i]
=> Array ( #before nil )
ひとつめのインスタンス変数(おそらく Super から継承したもの)に #before が代入されていることがわかります。
繰り返し使うので Object >> #inspect として定義しておくのもよいかもしれません。
Object addMethod
inspect ^ (1 to: self basicSize) asArray collect: [:i | self basicAt: i]
sub inspect
=> Array ( #before nil )
アクセッサの定義 その2(Sub >> #instVar、#instVar:)
前述、アクセッサの定義に従い、こんどは Sub に #instVar と #instVar: を定義します。
Sub addMethod
instVar ^ instVar
Sub addMethod
instVar: anObject instVar <- anObject
値の代入の様子を確認(その2)
sub inspect
=> Array ( #after nil )
むむ? どうしたことか、これまで(VisualWorks、Squeak)とは勝手が違うようです。バイトコードを確認してみたところ、原因がわかりました。
(Sub methodNamed: #instVar:) display
=> Method instVar: text instVar: anObject instVar <- anObject literals nil bytecodes ByteArray 33 2 1 96 6 0 245 15 5 241 15 1
資料によるとバイトコード 2 は引数の(スタックへの)PUSH、バイトコード 6 はインスタンス変数への(同じくスタックから)POP した値の代入のようです。したがって、6 0(= 96)は、2つあるうちの最初のインスタンス変数への代入を表わしています。念のため Super >> #instVar: を確認してみると、
(Super methodNamed: #instVar:) display
=> Method instVar: text instVar: anObject instVar <- anObject literals nil bytecodes ByteArray 33 2 1 96 6 0 245 15 5 241 15 1
こちらとまったく同じコードになってしまっています。これではいけません。ためしに、Sub >> #instVar: のバイトコードをいじって、2つめのインスタンス変数への代入になるように細工してみましょう。(ちょwwwおまwww)
(Sub methodNamed: #instVar:) basicAt: 3
=> ByteArray ( 33 96 245 241 )
((Sub methodNamed: #instVar:) basicAt: 3) at: 2
=> 96
((Sub methodNamed: #instVar:) basicAt: 3) at: 2 put: 97
(Sub methodNamed: #instVar:) display
=> Method instVar: text instVar: anObject instVar <- anObject literals nil bytecodes ByteArray 33 2 1 97 6 1 245 15 5 241 15 1
sub instVar: #again
sub inspect
=> Array ( #after #again )
うまくゆきました(こらこら…)。
ここで生じている(先だっての VisualWorks や Squeak との)違いは、Little Smalltalk のコンパイラの実装(残念ながら Smalltalk 自身でではなく、C 言語で実装されている…。parser.c)で、代入処理を記述する際、インスタンス変数へのアクセスに変数名を使っていることが原因と思われます。
static assignment(name) char *name; { int i; boolean done; done = false; /* it might be a temporary */ for (i = temporaryTop; (! done) && (i > 0); i--) if (streq(name, temporaryName[i])) { expression(); genInstruction(AssignTemporary, i-1); done = true; } /* or it might be an instance variable */ for (i = 1; (! done) && (i <= instanceTop); i++) if (streq(name, instanceName[i])) { expression(); genInstruction(AssignInstance, i-1); done = true; } if (! done) { /* not known, handle at run time */ genInstruction(PushArgument, 0); genInstruction(PushLiteral, genLiteral(newSymbol(name))); expression(); genMessage(false, 2, newSymbol("assign:value:")); } }
インスタンス変数名を重複させて使い分ける行為は、その是非以前に、そもそも Little Smalltalk では想定外なのでしょうね。
VisualWorks や Squeak と同じ挙動をさせるには、いったん Super のインスタンス変数を改名した状態でアクセッサを再定義し、その後、元にもどせばいけそうです。さっそく試してみましょう。
> ((Sub methodNamed: #instVar:) basicAt: 3) at: 2 put: 96 ByteArray ( 33 96 245 241 ) > sub instVar: #check Sub > sub inspect Array ( #check #again ) > Super variables at: 1 put: 'dummy' Array ( 'dummy' ) > Sub editMethod: #instVar Sub > Sub editMethod: #instVar: Sub > Super variables at: 1 put: 'instVar' Array ( 'instVar' ) > sub instVar #again > sub instVar: #final Sub > sub inspect Array ( #check #final ) > ((Sub methodNamed: #instVar:) basicAt: 3) at: 2 97
もくろみ通り。w(←ばか?)