Smalltalk 物見遊山の記録
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)
[ | 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 7send add: 8 <4E> store local 2; pop 9 <12> push local 2 10 <72> send asWordArray 11 <65> return
なんだか、某言語でSmalltalk型の文法を解するパーサを書いて、(またはCで書 いてプリミティブ化して)文字列/ストリームから読み込み、某言語の実行可能 配列を生成するような仕組みにしたら、某言語が高級言語処理系に化けるような…、 そんな予感。
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 のメタクラス…というようにクラスの数だけ存在する、いわばクラスの
幽波紋(スタンド)
のようなものらしい。(<どこが「いわば」じゃ)
| 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の連結メソッド(二項メッセージ)だったりする。
& * *= + += , - -= -> / // /= < << <= = == > >= >> @ \ \\ | ~= ~~
これを求めるためのプログラム。青木淳さんの 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 パターン(勝手に命名)に書き換えてみるのも 面白いかも。
どちらも、システム内のブラウザから参照するソースコードを、チャン ク(fileIn)形式でため込んでいる。
.soucesファイルはメジャーバージョンごとにリリースされるようだ。
.changesファイルは、イメージファイルと対になっている。
簡単に言うと、soucesファイルが幹で、changesファイルはブランチにあたる。 imageをsave as...で新しい名前で保存すると、世界が分岐する。
CVSなどと違うのは、changesが差分ではなく各世代をまるまる追記していく形を とっていること。イメージ中のバイトコードは、最新の当該ソース部分へのポイ ンタと関連づけて管理されているらしい。(どのレベルでかは不明)
あと、ソースの変更だけでなく、Do itの履歴もChangesに記録される。
これは、今のところ僕はブランチのスナップショットと捉えている。ただのス ナップショットと違うのは、あるプロジェクトと関係する変更だけが抽出されて いるという点だ。
プロジェクトというのは見た感じ WindowManager の VirtualDeskTop の様に見える。 Smalltalkでのプログラミングは大小のウィンドウがたくさん散らかってしまいが ちなので、やることごとに作業場所を作って整理しておくのは大切だ。
で、その作業場所がプロジェクトで、中で行った変更トランザクションのサマリが ChangeSet というわけだ。
なにかプログラムを作成したり、システムの拡張を行ったとき、プロジェクトの ChangeSetをファイルに落とせば、そのままパッチとして使うことができると思う。
外部からもらってきたChangeSetファイルを、システムに適応せずに中を覗いて見 る方法。
以前どうやるんだ? と思っていたやりかたが分かった。
open... -> file list でファイルリストを開き、見たいChangeSetファイルを選 択する。
右クリックメニューから browse changes を選択。
でオッケー。あとは、調べたいメソッドを選択して右クリックから compare to current とか、select conflicts with *** とか、いろいろ。(まだ詳細はよく わからん)
Interpreter translate: 'interp.c' doInlining: true.ちなみに、変換もとのクラスは Interpreter と ObjectMemory。 Cのコードを生 成するクラスは CCodeGenerator。 それらのカテゴリは "VMConstruction-Translation to C"。
SystemBrouser に慣れてくると、senders とか implementers とかで、どこから呼ばれているのか、 なにを呼んでいるのか、ポンポン開いて参照できる。
ブラウザのすごさが分かって来た…。
って、VC とか Del とか、世間一般の IDE を知らないから比較できないんだけど。
TheWorldMenu>>buildWorldMenu
中身を見ると大体構造が分かる。
#('メニュー項目の文字列' (レシーバ #メッセージセレクタ))
選択すると、レシーバにメッセージを送るというシンプルでカコイイ仕組み。
ザウ、モバなど非力なペンベースマシンでSqueakしている人は外すといいらしい。
[self anyButtonPressed] whileFalse: - [(Delay forMilliseconds: 50) wait]. + ["(Delay forMilliseconds: 50) wait"]. ^self cursorPoint
ここではまるっきり消してしまったが、ビジーループになるっぽい気がする。 50 を 5 とかに減らしてみたほうがいいのかも? これは [SML:4286] より。
"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."
手順をもうちょっと調べてみよう。
| 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のために使われるらしい。 要チェックやね。
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を実装するなら、このへんを 中心にオーバーライドする必要がある。
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 なんかのためにプログラムカウンタは必要になるね。
ソースを取って来る時同時にコンパイル&反映までやってしまうところが、 Smalltalkならでは。
この方式だと、最新VM でないと動かないような変更が入るとシステムが死んでし まうような気がする。
今回も途中で「これ以上進めると、3Dの部分が表示できなくなる。見たかったら VM を新しいものに変えろ」というダイアログが出ていたから、途中で警告は出す みたい。
VMも 3.1alpha build 6 に上げてみたところ、黒ベタになってしまってい たAlice-3Dもまた表示されるようになった。 Direct3D版とOpenGL版が同梱されて いた。
ワイルドカードによるマッチング
一瞬勘違いしてしまったが、self がパターンで、text が対象となる文字列であ ることに注意。
String>>beginsWith: prefix self が prefix で始まっていたら真。 String>>endsWith: suffix self が suffix で終っていたら真。 String>>endsWithAnyOf: aCollection self が aCollection の中のどれかで終っていたら真。その他、 findStringほげほげ: シリーズとか、 findTokensほげほげ: シリーズ とか、 indexOfほげほげ: シリーズとか。
結構充実している、というかちょっとずつ違うものがいっぱいあるぞ。
最初がファイルのコメントで、その後クラスの定義が複数続く。クラスの定義 は、クラスの宣言、クラスコメント、メソッドの宣言、メタクラスの宣言、クラ スメソッドの宣言、クラスの初期化文(多分、'クラス class initialize!') と いう並び。
メソッドの宣言は、
!クラス methodsFor: 'カテゴリ'!という行に、 '!' で区切ったそのカテゴリに属するメソッドの宣言が続き、カテ ゴリの最後は '! !' で終る。
クラスメソッドの場合は !クラス class methodsFor: 'カテゴリ'! となる。
fileInフォーマットのファイル読み込み処理を実際に担当しているところは、
VWNC3.0では... PeekableStream>>fileIn Squeakでは... PositionableStram>>fileInAnnouncing:'!' をデリミターとしてストリームを一区切り読み込み、
Compiler class>>evaluate:logged:に食わせている。