Home | Index | Changes | Diaries

Smalltalk Hichhikers Guide

Smalltalk 物見遊山の記録

Contents

Squeakでの配列の記法

{ 'home'. 'work'. 'services' }

VisualWorksでは #( 'home' 'work' 'services' ) という Lispっぽい書き方しか できない。しかし区切りがピリオドっちゅーのが…。

リテラル表現で、ネストした配列を書くときは内側のリストは '#' を省略できる。


#((1 2 3) (4 5 6))

==> #(#(1 2 3) #(4 5 6))



#((foo bar baz) (foo bar baz))

==> #(#(#foo #bar #baz) #(#foo #bar #baz))

うーん、Lispっぽ〜い。

#() は「リテラル配列」というもので、要素はすべてリテラルである必要がある。 (しかもブロックはリテラルなのに使用不可)

だから、配列の要素に変数や式の値を含めようという場合には使えない。

従来の Smalltalk の文法ではこういう場合どうしていたかというと、 Array の クラスメソッド with: を使う。(with: , with:with:, .... ときて with: x 6 まである)

あと要素が6個以上要素があるときは、 Array new add: xxx ; add: xxx ; add xxx; .... とするとか。

それが、 {} を使う場合は、ピリオドで区切られた式を要素に使うことができる ようだ。

まとめ:


#( (1 + 1) (2 + 2) )

=>  #(#(1 #+ 1) #(2 #+ 2))



Array with: 1 + 1 with: 2 + 2

=>  #(2 4)



{ 1 + 1 . 2 + 2 }

=> #(2 4)

Smalltalkと某言語

参考書にバイトコードを生成して逆アセンブルするメッセージが載っていたので それを眺めつつ。


[ | aStringA aStringB aStringC |

  aStringA := 'Hello '.

  aStringB := 'Smalltalk world.'.

  aStringC := aStringA add: aStringB.

  aStringC asWordArray.

] method symbolic



 'normal CompiledBlock numArgs=0 numTemps=3 frameSize=12



literals: (''Hello '' ''Smalltalk world.'' #asWordArray )



1 <1C> push ''Hello ''

2 <4C> store local 0; pop

3 <1D> push ''Smalltalk world.''

4 <4D> store local 1; pop

5 <10> push local 0

6 <11> push local 1

7  send add:

8 <4E> store local 2; pop

9 <12> push local 2

10 <72> send asWordArray

11 <65> return

なんだか、某言語でSmalltalk型の文法を解するパーサを書いて、(またはCで書 いてプリミティブ化して)文字列/ストリームから読み込み、某言語の実行可能 配列を生成するような仕組みにしたら、某言語が高級言語処理系に化けるような…、 そんな予感。

Smalltalkの変数

どこかでだれかが「きぼーん」と書いていたような :D

Smalltalkの変数は、大まかにいって3種類あると思う。

ローカル変数

これが一番分かりやすい。Smalltalkの(文法要素としての)programの冒頭部、

'|' で囲む部分で宣言される。(Rubyのブロックの記法にも受け継がれてる)

Lispでいう


      (let (foo bar...) ****) の (foo bar...)

にあたる。ただし初期化の構文はない。

バイトコンパイルされた結果を観察するに、ローカルのスタックフレームに宣言 した変数のぶんだけ領域が確保され、VMの命令では


      store local n

      push local n

(nは0オリジンのインデクス)といった形で読み書きされるような気がする。

大域変数

Smalltalk という名前のオブジェクトがあって、これがシステム全体の大域辞書 になっている。エントリはシステム内の全クラス。Smalltalk 自体は SystemDictionaryクラスのソルインスタンス。(今風にいうとSingleton)

つまり Smalltalk-80 レベルのアーキテクチャでは、クラス名は一つのグローバ ルな名前空間しか持てないということになる。(最新のVisualWorksとかではどー なっているか知らない)


Smalltalk at: #fuga put: 'hoge'.

とやって単なる大域変数として使えなくもないが気持ち悪いし恐いのでやっては いけない。(否定の否定の否定)

危険な賭じゃが、勉強のために敢えてやってみよう。(Cyborg G-chan風に)


Smalltalk at: #hogehogehoge put: 1.  "値のセット"

Smalltalk at: #hogehogehoge          "値の取り出し"

 => 1

hogehogehoge                         "直で叩いてみる"

 => 1

やはり、Smalltalk辞書に存在するエントリは、裸のシンボルをそのまま評価して も名前解決されるようだ。クラス名を書くとクラスの実体が返ってくるのはこの 辞書を検索していることが分かる。

Smalltalk辞書そのものはクラスではないが、上で「Smalltalk という名前のオブ ジェクト」と言ったその名前は、クラスと同じ名前空間内にある。 Smalltalk辞 書のエントリ #Smalltalk が自分自身を指しているからだ。

分かりやすく言うとこれがヘビの尻尾なんです。(<余計分かりにくいです。)

ちなみに、

Smalltalk := nil.

というハットトリック(ただし自殺点)も存在するらしい。:D

クラス・オブジェクトに関る変数

クラス変数、インスタンス変数、クラスプール変数(詳細不明)など。

Smalltalkでは「メンバ」はすべて protected で、「アクセサ」は慣習的に get がメンバ名と同じ、 set はメンバ名 + ':' となる。


aPerson name. => インスタンス変数 name の値を返す

aPerson name: 'new name'. => インスタンス変数 name に文字列 'new name' を設定する

あとずっと誤解していたのが、メタクラスというものは Integer のメタクラス、 String のメタクラス…というようにクラスの数だけ存在する、いわばクラスの

幽波紋(スタンド)

のようなものらしい。(<どこが「いわば」じゃ)

今日みつけたCOOLなコード

Squeakより一部抜粋してお送りします。


| answer writer iter |

answer := String streamContents: [ :strm |

        writer := [ :msg :doer |

                ms := [iter timesRepeat: doer] timeToRun.

                strm nextPutAll: msg,((ms * 1000 / iter) roundTo: 0.01) printString,' usec'; cr.

        ].

        :

        iter := 1000000.

        writer value: 'empty loop ' value: [self].

        writer value: 'modulo ' value: [12345678 \ 256].

        writer value: 'bitAnd: ' value: [12345678 bitAnd: 255].

        :

].

StringHolder new contents: answer; openLable: 'send/receive stats'.

ブロックの実行にかかった時間を計測し表示するというメトリクス用のメソッ ド。こういう「手続きを渡して結果を報告させる」という書き方は Lisp方面の由 来だろうか。CやBASICばかりかじっていても到底でてこない発想だと思ったり。

全貌は、


 Morphic-Remote>>CanvasEncoder class>>timeSomeThings

をどうぞ。

ちなみに、 ','(カンマ)はSequenceableCollectionの連結メソッド(二項メッセージ)だったりする。

Binary Message is not a Operator.

Squeakで定義されている二項メッセージの一覧。


  &   *   *=  +   +=  ,   -

  -=  ->  /   //  /=  <   <<

  <=  =   ==  >   >=  >>  @

  \   \\  |   ~=  ~~

これを求めるためのプログラム。青木淳さんの Smalltalk Ideoms -- chapter2 よりSqueakで動くようにちょっと修正。


| aCollection aStream |

aCollection := Set new.

Smalltalk allBehaviorsDo: [:each | aCollection addAll: each selectors].

aCollection := aCollection select: [:each |

                each numArgs = 1 and: [each isKeyword not]].

aCollection := aCollection asSortedCollection asArray.

aStream := WriteStream on: String new.

aCollection do: [:each | aStream nextPutAll: each asString; cr].

StringHolder new contents: aStream contents; openLabel: 'Binary Messages'.

^aCollection

昨日の string - stream - writer パターン(勝手に命名)に書き換えてみるのも 面白いかも。

soucesとchanges

Squeakの処理系には.soucesファイルと.changesファイルが存在する。

どちらも、システム内のブラウザから参照するソースコードを、チャン ク(fileIn)形式でため込んでいる。

.soucesファイルはメジャーバージョンごとにリリースされるようだ。

.changesファイルは、イメージファイルと対になっている。

簡単に言うと、soucesファイルが幹で、changesファイルはブランチにあたる。 imageをsave as...で新しい名前で保存すると、世界が分岐する。

CVSなどと違うのは、changesが差分ではなく各世代をまるまる追記していく形を とっていること。イメージ中のバイトコードは、最新の当該ソース部分へのポイ ンタと関連づけて管理されているらしい。(どのレベルでかは不明)

あと、ソースの変更だけでなく、Do itの履歴もChangesに記録される。

ChangeSet

もうひとつ似たものに ChangeSet がある。

これは、今のところ僕はブランチのスナップショットと捉えている。ただのス ナップショットと違うのは、あるプロジェクトと関係する変更だけが抽出されて いるという点だ。

プロジェクトというのは見た感じ WindowManagerVirtualDeskTop の様に見える。 Smalltalkでのプログラミングは大小のウィンドウがたくさん散らかってしまいが ちなので、やることごとに作業場所を作って整理しておくのは大切だ。

で、その作業場所がプロジェクトで、中で行った変更トランザクションのサマリが ChangeSet というわけだ。

なにかプログラムを作成したり、システムの拡張を行ったとき、プロジェクトの ChangeSetをファイルに落とせば、そのままパッチとして使うことができると思う。

ChangeSetの閲覧(Squeak篇)

外部からもらってきたChangeSetファイルを、システムに適応せずに中を覗いて見 る方法。

以前どうやるんだ? と思っていたやりかたが分かった。

open... -> file list でファイルリストを開き、見たいChangeSetファイルを選 択する。

右クリックメニューから browse changes を選択。

でオッケー。あとは、調べたいメソッドを選択して右クリックから compare to current とか、select conflicts with *** とか、いろいろ。(まだ詳細はよく わからん)

Squeak VM C Code Generator

Smalltalk で書かれた Squeak VM のコードを C に変換するメッセージ式

Interpreter translate: 'interp.c' doInlining: true.

ちなみに、変換もとのクラスは Interpreter と ObjectMemory。 Cのコードを生 成するクラスは CCodeGenerator。 それらのカテゴリは "VMConstruction-Translation to C"。

SystemBrouser に慣れてくると、senders とか implementers とかで、どこから呼ばれているのか、 なにを呼んでいるのか、ポンポン開いて参照できる。

ブラウザのすごさが分かって来た…。

って、VC とか Del とか、世間一般の IDE を知らないから比較できないんだけど。

Squeakデスクトップメニューの正体

クラスは TheWorldMenu 。第一階層の生成式は

TheWorldMenu>>buildWorldMenu

中身を見ると大体構造が分かる。


#('メニュー項目の文字列' (レシーバ #メッセージセレクタ))

選択すると、レシーバにメッセージを送るというシンプルでカコイイ仕組み。

マウス入力にウエイト[Squeak]

InputSensor>>waitButton に、50ミリセカンドのウェイトが入っている。

ザウ、モバなど非力なペンベースマシンでSqueakしている人は外すといいらしい。


         [self anyButtonPressed] whileFalse:

-                [(Delay forMilliseconds: 50) wait].

+                ["(Delay forMilliseconds: 50) wait"].

         ^self cursorPoint

ここではまるっきり消してしまったが、ビジーループになるっぽい気がする。 50 を 5 とかに減らしてみたほうがいいのかも? これは [SML:4286] より。

Squeakのためのフォントコンバータ

BDFFontReader class>>convertFilesNamed:toFamilyNamed:inDirectoryNamed:

"This utility converts X11 BDF font files to Squeak .sf2 StrikeFont files." だそうで。


TTFontReader>>readFrom: aStream -> TTFontDescription

TTFontDescription>>asStrikeFontScale: scale -> FormSetFonts

"Generate a StrikeFont (actually a FormSetFont) for this TTF font at a given scale."

手順をもうちょっと調べてみよう。

VisualWorksにできてSqueakにできないことの例

ブロックの再帰


| aBlock |

aBlock := [:i |

     i < 1 ifTrue: [

          0

     ] ifFalse: [

          i + (aBlock value: i - 1)

     ]].

aBlock value: 10.

 ==> 55

SML:2602参照。

もともとSmalltalk-80の仕様では[](ブロック)はBlockContextというクラス で、ブロック内のテンポラリ変数は局所化されておらず、外側のコンテキストか ら丸見えになってしまっていた。もちろん再帰不能。

市場環境で進化していったVisualWorksではそれがBlockClosureというものに置き 換えられ、ブロックがLisp系でいうところの真のクロージャとして使えるように なったそうな。

Smalltalk-80をベースに再スタートをきったSqueakは、まだそこまでたどり着い ていないということらしい。

※fixTemp というメッセージが、ここらへんのwork aroundのために使われるらしい。 要チェックやね。

Paragraph Editor

MVCにおいてWorkSpaceなどのテキスト入力を司っているのはParagraphEditor


dispatchOnCharacter: char with: typeAheadStream

    "Carry out the action associated with this character, if any.

    Type-ahead is passed so some routines can flush or use it."

ここでコントロールキーなどの特殊機能を定義しているようだ。

キーバインドの変更や、例えばSKKのような独自IMEを実装するなら、このへんを 中心にオーバーライドする必要がある。

今、Smalltalkで作るBASIC

昔懐かしい N-BASIC のように行番号から始まるインストラクションを入力すると プログラムとして登録される雰囲気。

 10 PRINT: 'hello'.

 20 GOTO: 10.

SmallIntegerのインスタンスメソッドとしてBASICインストラクション風のキーワ ードメッセージを追加する。各命令は、新規クラスBasicInterpreterのクラス変 数 BasicProgram(a Dictionary) に、self をキーとして実行すべき式を追加す る。例えば PRINT: は、


PRINT: aStirng

  BasicInterpreter

    at: self

    block: [Transcript show: aString; cr]

    source: 'PRINT: ''', aString, ''''.

  Transcript cr; show: 'OK'; cr

…かなりアドホックだな。

実行は BasicInterPreter class>>RUN

BasicInterpreterの実装は、BasicProgram の内容をキーの昇順に value してい けばいいか。

GOTO や FOR...NEXT なんかのためにプログラムカウンタは必要になるね。

Squeakの開発版を追いかける

[help...] メニューから、[update code from server] で、リポジトリから最新 のコードを取って来れるが、 ChangeSetの一覧を見てみると、 "4282envRefix- raa" のように連番+概要+イニシャルで細かい(コミット単位?) 変更ごとのチェ ンジセットとしてサーバーに登録されているようだ。

ソースを取って来る時同時にコンパイル&反映までやってしまうところが、 Smalltalkならでは。

この方式だと、最新VM でないと動かないような変更が入るとシステムが死んでし まうような気がする。

今回も途中で「これ以上進めると、3Dの部分が表示できなくなる。見たかったら VM を新しいものに変えろ」というダイアログが出ていたから、途中で警告は出す みたい。

VMも 3.1alpha build 6 に上げてみたところ、黒ベタになってしまってい たAlice-3Dもまた表示されるようになった。 Direct3D版とOpenGL版が同梱されて いた。

文字列のマッチング

String>>match: text

ワイルドカードによるマッチング

一瞬勘違いしてしまったが、self がパターンで、text が対象となる文字列であ ることに注意。


String>>beginsWith: prefix



 self が prefix で始まっていたら真。



String>>endsWith: suffix



 self が suffix で終っていたら真。



String>>endsWithAnyOf: aCollection



 self が aCollection の中のどれかで終っていたら真。

その他、 findStringほげほげ: シリーズとか、 findTokensほげほげ: シリーズ とか、 indexOfほげほげ: シリーズとか。

結構充実している、というかちょっとずつ違うものがいっぱいあるぞ。

fileIn(チャンク)形式のフォーマット

基本的には通常の '.' の代わりに '!' をデリミタとした Smalltalk の実行文。

最初がファイルのコメントで、その後クラスの定義が複数続く。クラスの定義 は、クラスの宣言、クラスコメント、メソッドの宣言、メタクラスの宣言、クラ スメソッドの宣言、クラスの初期化文(多分、'クラス class initialize!') と いう並び。

メソッドの宣言は、


      !クラス methodsFor: 'カテゴリ'!

という行に、 '!' で区切ったそのカテゴリに属するメソッドの宣言が続き、カテ ゴリの最後は '! !' で終る。

クラスメソッドの場合は !クラス class methodsFor: 'カテゴリ'! となる。

fileInフォーマットのファイル読み込み処理を実際に担当しているところは、


VWNC3.0では... PeekableStream>>fileIn

Squeakでは... PositionableStram>>fileInAnnouncing:

'!' をデリミターとしてストリームを一区切り読み込み、

Compiler class>>evaluate:logged:

に食わせている。

Emacs Like Keys for Squeak

EmacsLikeKeysForSqueak ... 別ページへ