Hatena::ブログ(Diary)

Smalltalkのtは小文字です

id:sumim:about

Smalltalk を本格的に勉強する気はないけれど、うんちく程度に知っておきたいなら→Smalltalkをちょっとかじってみたい人のための、チュートリアルまとめ - Qiita

オブジェクト指向の“モヤッと”の正体を知りたくなったらこちらの記事が役に立つかも→id:sumim:20080415:p1 とか id:sumim:20040525:p1


 

2017-05-26

[] 型クラスの現在のところの理解を Squeak Smalltalk で表現してみる


ここ数日で、Scala の implicit parameter がどうやって型クラスっぽいことを実現しているかというのが分かったような気になってきたのと、型クラスが「状態を扱わないメソッドの集合」であるということを聞いて「それってまさにトレイトそのもの(ただし Scala のそれではなくシェルリの)じゃないか!」とも思ったので、Squeak(あるいは Pharo)のトレイトを使って型クラスを表現してみるとどうなるか試して遊んでみました。もとより動的型の Smalltalk で何ができるわけでもないので、あくまで雰囲気だけですが。


以降に示す各式は Squeak の Workspace に適宜貼り付けて(一挙ではなく)順に評価( do it、あるいは結果が欲しければ print it )すると実行できます。

Pharo の Playground でもいちおう動作しますが、Pharo にはなぜか #uses: がないので、あらかじめ次式を評価して Squeak 同様の #uses: を追加しておく必要があります。

ClassDescription compile: 'uses: trait self setTraitComposition: trait asTraitComposition'

あと念のため、Smalltalk環境(IDEモドキ)の最小限の操作だけで遊べるように、クラスやトレイトの定義やそれらへのメソッドの追加はすべて式で、例えば

(トレイトの定義の式) compile: 'メソッドの定義'

というように表現しています。本来であれば、クラスブラウザなどの GUI ツールを用いるのがデフォ(このため GNU Smalltalk などの特殊な処理系を除き、Smalltalk にはクラスやメソッド定義のための構文が無い!)なのでこの点もどうぞあしからず。


まず型クラス Eq を模したトレイト TCEq として定義します。ちなみに Eq の定義は Haskell ではこうです。(Hugs.Prelude から抜粋。下の Ord も同じ)

class Eq a where
    (==), (/=) :: a -> a -> Bool

    -- Minimal complete definition: (==) or (/=)
    x == y      = not (x/=y)
    x /= y      = not (x==y)

この遊びでは既存のメソッドとかぶらないように EQ: NEQ: などとしました。

"type class Eq"
(Trait named: #TCEq uses: #() category: 'TypeClasses-Simulation')
compile: 'EQ: other
   ^(self NEQ: other) not';
compile: 'NEQ: other
   ^(self EQ: other) not'.

次に同じく型クラス Ord を模したトレイト TCOrd を定義します。Haskell の Ord の定義はこちら。

class (Eq a) => Ord a where
    compare                :: a -> a -> Ordering
    (<), (<=), (>=), (>)   :: a -> a -> Bool
    max, min               :: a -> a -> a

    -- Minimal complete definition: (&lt;=) or compare
    -- using compare can be more efficient for complex types
    compare x y | x==y      = EQ
		| x<=y      = LT
		| otherwise = GT

    x <= y                  = compare x y /= GT
    x <  y                  = compare x y == LT
    x >= y                  = compare x y /= LT
    x >  y                  = compare x y == GT

    max x y   | x <= y      = y
	      | otherwise   = x
    min x y   | x <= y      = x
	      | otherwise   = y

Ord は Eq を継承しているので TCOrd も TCEq を #uses: します。

"type class Eq => Ord"
(Trait named: #TCOrd uses: TCEq category: 'TypeClasses-Simulation')
compile: 'CMP: other
   ^true caseOf: {
      [self EQ: other] -> [#EQ].
      [self LE: other] -> [#LT]
   } otherwise: [#GT]';
compile: 'LE: other
   ^(self CMP: other) ~= #GT';
compile: 'LT: other
   ^(self CMP: other) == #LT';
compile: 'GE: other
   ^(self CMP: other) ~= #LT';
compile: 'GT: other
   ^(self CMP: other) == #GT';
compile: 'MAX: other
   ^(self LE: other) ifTrue: [other] ifFalse: [self]';
compile: 'MIN: other
   ^(self LE: other) ifTrue: [self] ifFalse: [other]'.

次にインスタンス Ord Int に相当するトレイト TOrdInteger を定義します。型クラスとそのインスタンスの関係からこれはクラスで表現したほうが相応しいかとも思ったのですが、後のことを簡単に済ませたいのでここはあえてトレイトにします。

型クラス Ord のインスタンスは #CMP: か #LE: を自前で用意する必要があると同時に、Ord は Eq のサブクラスでもあることから #EQ: か #NEQ: も同様に自前で用意する必要があります。ここではそれぞれ #LE: と #EQ: を定義しました。

"type class instance Ord Integer"
(Trait named: #TOrdInteger uses: TCOrd category: 'TypeClasses-Simulation')
compile: 'EQ: other
   ^self = other';
compile: 'LE: other
   ^self <= other'.

前述の“後のこと”こと、型クラスを機能させるための“マジック”については、ここでは簡単のためトレイトを #uses: することで代用しました。ちなみに Haskell だとここで暗黙の引数にメソッド辞書を、Scala なら辞書代わりのオブジェクトを渡すよう裏で細工が(ただしこれらの言語ではコンパイル時に)なされます。

Integer uses: TOrdInteger. "use magic"

では準備が整いましたので動作の確認をします。

3 EQ: 3. "=> true "
3 EQ: 4. "=> false "

3 NEQ: 3. "=> false "
3 NEQ: 4. "=> true "

3 CMP: 3. "=> #EQ "
3 CMP: 4. "=> #LT "
4 CMP: 3. "=> #GT "

3 LE: 3. "=> true "
3 LE: 4. "=> true "
4 LE: 3. "=> false "

3 LT: 3. "=> false "
3 LT: 4. "=> true "
4 LT: 3. "=> false "

3 GE: 3. "=> true "
3 GE: 4. "=> false "
4 GE: 3. "=> true "

3 GT: 3. "=> false "
3 GT: 4. "=> false "
4 GT: 3. "=> true "

3 MAX: 3. "=> 3 "
3 MAX: 4. "=> 4 "
4 MAX: 3. "=> 4 "

3 MIN: 3. "=> 3 "
3 MIN: 4. "=> 3 "
4 MIN: 3. "=> 3 "

うまく動いていますね。


ついでに #MEMBER: や #SORT も定義しようかと思ったのですが、当然のことながら Smalltalk でレシーバーに Eq a => [a] や Ord a => [a] といった縛りは設けられないためやむなく Array に定義するしかなく、ここまでそれっぽく動いていたのと比べるとかなり興ざめです。

Array
compile: 'MEMBER: y
   self ifEmpty: [^false].
   ^(self first EQ: y) or: [self allButFirst MEMBER: y]';
compile: 'SORT
   | pivot |
   self ifEmpty: [^self].
   pivot := self middle.
   ^(self select: [:x | x LT: pivot]), {pivot}, (self select: [:x | x GT: pivot])'.
#(1 2 3) MEMBER: 3. "=> true "
#(3 2 1) SORT. "=> #(1 2 3) "

次に Ord a => Ord [a] に対応するインスタンスを作りたかったのですがやはり無理なので Ord [Int] 相当で我慢します。トレイト OrdIntegerArray を作り同様のマジックを IntegerArray に使ってみましょう。

"type class instance Ord [Int]"
(Trait named: #TOrdIntegerArray uses: TCOrd category: 'TypeClasses-Simulation')
compile: 'EQ: other
   (self isEmpty and: [other isEmpty]) ifTrue: [^true].
   (self isEmpty or: [other isEmpty]) ifTrue: [^false].
   ^(self first EQ: other first) and: [self allButFirst EQ: other allButFirst]';
compile: 'LE: other
   self ifEmpty: [^true].
   other ifEmpty: [^false].
   ^(self first LT: other first) or: [
		(self first EQ: other first) and: [self allButFirst LE: other allButFirst]]'.
IntegerArray uses: TOrdIntegerArray. "use magic"

これで、IntegerArray 同士の等価や大小比較が可能になります。以下、動作確認です。

#(1 2 3) asIntegerArray EQ: #(1 2 3) asIntegerArray. "=> true "
#(1 2 3) asIntegerArray EQ: #(1 3 2) asIntegerArray. "=> false "

#(1 2 3) asIntegerArray NEQ: #(1 2 3) asIntegerArray. "=> false "
#(1 2 3) asIntegerArray NEQ: #(1 3 2) asIntegerArray. "=> true "

#(1 2 3) asIntegerArray CMP: #(1 2 3) asIntegerArray. "=> #EQ "
#(1 2 3) asIntegerArray CMP: #(1 3 2) asIntegerArray. "=> #LT "
#(1 3 2) asIntegerArray CMP: #(1 2 3) asIntegerArray. "=> #GT "

#(1 2 3) asIntegerArray LE: #(1 2 3) asIntegerArray. "=> true "
#(1 2 3) asIntegerArray LE: #(1 3 2) asIntegerArray. "=> true "
#(1 3 2) asIntegerArray LE: #(1 2 3) asIntegerArray. "=> false "

#(1 2 3) asIntegerArray LT: #(1 2 3) asIntegerArray. "=> false "
#(1 2 3) asIntegerArray LT: #(1 3 2) asIntegerArray. "=> true "
#(1 3 2) asIntegerArray LT: #(1 2 3) asIntegerArray. "=> false "

#(1 2 3) asIntegerArray GE: #(1 2 3) asIntegerArray. "=> true "
#(1 2 3) asIntegerArray GE: #(1 3 2) asIntegerArray. "=> false "
#(1 3 2) asIntegerArray GE: #(1 2 3) asIntegerArray. "=> true "

#(1 2 3) asIntegerArray GT: #(1 2 3) asIntegerArray. "=> false "
#(1 2 3) asIntegerArray GT: #(1 3 2) asIntegerArray. "=> false "
#(1 3 2) asIntegerArray GT: #(1 2 3) asIntegerArray. "=> true "

#(1 2 3) asIntegerArray MAX: #(1 2 3) asIntegerArray. "=> an IntegerArray(1 2 3) "
#(1 2 3) asIntegerArray MAX: #(1 3 2) asIntegerArray. "=> an IntegerArray(1 3 2) "
#(1 3 2) asIntegerArray MAX: #(1 2 3) asIntegerArray. "=> an IntegerArray(1 3 2) "

#(1 2 3) asIntegerArray MIN: #(1 2 3) asIntegerArray. "=> an IntegerArray(1 2 3) "
#(1 2 3) asIntegerArray MIN: #(1 3 2) asIntegerArray. "=> an IntegerArray(1 2 3) "
#(1 3 2) asIntegerArray MIN: #(1 2 3) asIntegerArray. "=> an IntegerArray(1 2 3) "

まったく面白みはないですが、いちおう #MEMBER:、#SORT も動きます。

{#(1 3 2) asIntegerArray. #(1 2 3) asIntegerArray. #(2 3 1) asIntegerArray} MEMBER: #(1 3 2) asIntegerArray. "=> true "
{#(1 3 2) asIntegerArray. #(1 2 3) asIntegerArray. #(2 3 1) asIntegerArray} SORT.
"=> {an IntegerArray(1 2 3) . an IntegerArray(1 3 2) . an IntegerArray(2 3 1)} "

型クラスの肝である“マジック”の部分、つまり既存の定義をいじらずに拡張できる細工をどう裏で実現するかはいろいろあっても良さそうです。また、エンティティとしての型クラスが、デフォルト実装を持てるインターフェイスや(Scalaのではなくシェルリの)トレイトが機能面で共有する類似性に個人的にはとても興味惹かれます。


参考:

2017-04-06

[] 〜ect:で終わるセレクター(Rubyで言うところの〜ect系のメソッド名)にはどんなものがあるか、あらためてSqueak Smalltalkで調べてみた


アーロ・ガスリーの「アリスのレストラン」の歌詞にインスパイアされて Smalltalk-80 から使われ始めたとされる collect: 、select: 、inject:into: など Ruby で言うところの Smalltalk 由来の 〜ect系のメソッド名(Smalltalk では「メッセージセレクター」あるいは単に「セレクター」)ですが、そういえば当該歌詞にも登場する inspect も広い意味では 〜ect系と考えてよさそうだな、他にも何かないのかな…とふと気になったので、まだ知らない便利な 〜ect:系セレクターの発見もあるかも!と改めて調べてみました。

結果としては、既に把握している collect:、select:、reject:、detect:、inject:into: とその変種の他には目新しいものは見つからなかったわけですが(当然と言えば当然か…)、せっかくなので使った式と結果をメモとして下に残しておきます。^^;


余談ですが、件の歌詞には inject、inspect、detect、select、inspect は登場するものの、collect や reject は見当たらない(では、後者はさておき collect: はどこから?)のもちょっとだけ気になります。



▼ コレクションクラスで使われている ect: を含むセレクター群

(Array streamContents: [:ss |
   (Collection withAllSubclasses remove: Matrix; yourself) do: [:class |
      ss nextPutAll: (
         class selectors select: [:sel | (sel includesSubString: 'ect:')
            and: [(sel asLowercase includesSubString: 'object') not]])
   ]
]) asSet

Matrix はちょっと特殊なので除きました。

"=> a Set(#collect:thenSelect: #collect:from:to: #pairsCollect: #collect:into: #select:thenCollect: 
#collect: #detect:ifNone: #overlappingPairsCollect: #groupsOf:atATimeCollect: #inject:into: 
#select:thenDo: #withIndexCollect: #select: #collect:as: #detect:ifFound:ifNone: #collect:thenDo: 
#traitsCollect: #valuesCollect: #reject: #detect: #associationsSelect: #with:collect: 
#reject:thenDo: #regex:matchesCollect:) "

▼ 〜ect 的なセレクターには他にどんなものがあるのか?

(Array streamContents: [:ss |
   SystemNavigation default allBehaviorsDo: [:class |
      ss nextPutAll: (
         class selectors select: [:sel | (sel includesSubString: 'ect')
            and: [#(object project rect expect aspect effect connect intersect 
                  perfect subject selection ector section defect dissect infect 
                  protect dialect collect select inject reject detect) 
               noneSatisfy: [:NG | sel asLowercase includesSubString: NG]]
         ])
   ]
]) asSet

コレクションの〜ect系と、ectを含む単語を除いて集計してみましたが、実質 inpect しか見つかりませんでした。

"=>  a Set(#inspectIt: #inspectPointers #inspectCurrentStack #inspectOwnerChain 
#test06InspectIt #inspect: #inspectForm #inspectCurrentBackground #inspectInstances 
#inspectOnCount: #inspectModel #inspectIt #inspectProcess #inspectCurrentCard #basicInspect: 
#inspectWorkingCopy #inspectMethod #inspectArgumentsPlayerInMorphic: #basicInspect #inspect 
#inspectWorldModel #inspectInMorphic: #inspectViewee #inspectWithLabel: #inspectUntilCount: 
#inspectAt:event: #inspectOnce #inspectChangeSet #inspect:label: #inspectParameters 
#inspectInMorphic #inspectTestVars #inspectBasic #inspectContext #inspectSubInstances 
#inspectReceiver #inspectFirstSubView #inspectAllInstances #smallInspectItIconContents 
#inspectPreferences #inspectView #inspectKey #inspectElement #inspectFormDictionary 
#inspectBindings #inspectMember #inspectIt:result: #smallInspectItIcon #doExpiredInspectCount)"

2016-08-30

[] Squeak 5.1 をエディタ代わりに使い始めたので気付いたことを記録


ちょっとしたことを書くときに使うようにエディタ代わりに立ち上げている Squeak環境を 4.3J から久しぶりに 5.1 に更新して気付いたこと。



▼ duplicate があっさり殺されていた件

duplicate は、任意のテキストを選択→ alt-shift-d というキー操作で、選択操作の直前のキャレットの位置に選択テキストを複製して挿入する機能なのですが、よりよく使われるであろう debug it にキーアサイン奪われた上に、あろうことかメソッド(#duplicate:)ごと削除の憂き目をみていたようです。ペーストバッファを汚さないので個人的には気に入って使っていた機能だったので、Squeak 5.1 をエディタ代わりに使い始めて、まず最初にこのことに気がつきました。残念なことです。

カット、コピー、ペースト、アンドゥなどと違い、duplicate は後述の alt-j のモードレス検索や alt-e のスワップ機能(選択テキストを直前の選択テキストと交換)と共に、Mac/Win に継承されず、知らない人は一生知る機会もなく、ともすれば不要と思われがちな編集機能三兄弟なので、again のモードレス検索が殺されそうになったときにこちらも同時に気をつけておくべきでした。ということで、アラン・ケイがむかし Scheme で書いたものを移植したと言われる手書き認識(!?)が外されて以降、空席のままになっているっぽい alt-r に復活させました。

同様に、alt-shift-z を redo に奪われた #makeCapitalized: についても、あればあったで地味に便利なので、こちらも alt-shift-q あたりに復活しておきました。




▼ alt-shift-r、alt-shift-l による編集行もしくは選択行一括インデント/アウトデントのキーアサインが tab、shift-tab に変更&選択時のみ機能に制限

alt-shift-r、l が使えないことに気付いて、さすがにこの機能がなくなるとも思えないので、どうするのかと思ったら、1行以上の複数行を選択して tab 、shift-tab を押すのに変わっていました。alt-shift-r は行を選択しなくても機能していたので1行のみの場合に限っては不便だったのでその意味では改悪ですが、まあ許容範囲でしょうか。なお当該機能は今回のキーアサイン変更の前から行内の文字数が1文字だと機能しないバグがあるのでフィックスしておきました。


ちなみに空いた alt-shift-r、alt-shit-l はそれぞれ Recent Submissions 、File List の呼び出しに転用された模様です。alt-k(ワークスペース起動)、alt-t(トランスクリプト呼び出し)などと違い、テキスト編集中でも使える類似の「何かを起動する」タイプのキーショートカットの一覧はこちら。


    • alt-shift-l → File List
    • alt-shift-o → Monticello Browser
    • alt-shift-p → Preference Browser
    • alt-shift-r → Recent Submissions

alt-k、alt-t など、デスクトップをクリックするなどしてキーボードのフォーカスを明示的に外せば使えるショートカットを含め、PasteUpMorph>>#defaultDesktopCommandKeyTriplets で確認できます。



▼ again (alt-j) によるモードレス検索が alt-g に移動

これはすでに以前 Qiita で書いた通り。検索テキストをダイアログボックスなど UI をいっさい使わずに、おもむろにタイプした文字列を again (alt-j)で検索できる機能が Apple Smalltalk-80 時代からあり、Squeak にも継承されたこの機能を知って以来ずっと愛用していたのですが、Squeak 5.0 からこの使い方は again からは分離され、代わりに find the current selection again (alt-g) に引き継がれました。削除を含むテキスト置き換えの操作を繰り返す again 機能はそのまま alt-j(一括は alt-shift-j )で使えます。

余談ですが、新しい again では、一度 alt-j(alt-shift-j)をタイプしてヒットしたパターンが選択状態になってからもう一度 alt-j(同じく alt-shift-j) をタイプすると、改めて再度置換(同、一括置換)になるという二段階になったので要注意です。



▼ alt - 1〜4 のフォントサイズ変更にいつの間にかなんか変な機能が割り振られているw

もっとも alt-1〜5 のフォントサイズ変更の機能にはバグがあっていまままで正常に動作していなかったので潰されても文句は言えませんが…。^^; 新しい機能は、ブラウザのコードペイン(つまりメソッド定義時)での使用を念頭においた機能で、一行目にメッセージパターンとして宣言された仮引数をキャレットの位置に挿入する機能。何番目の仮引数を挿入するかが 1〜4 に対応しています(SmalltalkEditor>>#typeMethodArgument:)。

これはこれで便利な機能なのかもしれませんが、メソッド定義時以外は意味をなしませんし(実際、ワークスペースなどで普通の文章を打っているときに試したときは何が起こっているか分かりませんでした)、伝統的な alt-1〜4 によるフォントサイズ変更(オリジナルの Smalltalk-80 ではフォント変更の機能も兼ねていた)を潰してしまうのもけしからんことなので、コードペインのときだけ機能するように細工しました。せっかくなので alt-1〜5 も機能するように修正しました。ほぼ使わないとは思いますが、コードペインでも alt-shift-1〜4 で通常の alt-1〜4 の機能も使えるようにもしておきました。




▼ .txt のエンコーダーを utf8 に、.html で保存、読み込みの機能を拡張

エディタ代わりに使うときに、開いてからいちいちエンコーダーを utf8 に変えるのは面倒なので、.txt のときは自動的に UTF8TextConverter を選ぶように細工します。

FileList >> defaultEncoderFor: aFileName

    "This method just illustrates the stupidest possible implementation of encoder selection."
    | l |
    l := aFileName asLowercase.
"    ((l endsWith: FileStream multiCs) or: [
        l endsWith: FileStream multiSt]) ifTrue: [
        ^ UTF8TextConverter new.
    ].
"
    ((l endsWith: FileStream cs) or: [
        l endsWith: FileStream st]) ifTrue: [
        ^ MacRomanTextConverter new.
    ].

    (l endsWith: 'txt')
        ifTrue: [^ UTF8TextConverter new].

    ^ Latin1TextConverter new.


あと、これもすでに以前書いたものですが、せっかくフォントサイズを変えたりカラーを変えても保存する手段がないのも悲しいので、ワークスペースなどを右クリック→ more... → save contents to file... で .html 付きで保存したときに HTML 出力する機能と、それを読み込んだ(File List の右クリックで workspace with contents)ときプロパティを再現する機能を追加しました。




▼オーサーイニシャルのペーストが undo できないバグがあったので修正

alt-shfit-v でオーサーイニシャルをタイムスタンプ付きでペーストする機能があります。前述の duplicate を復活させている作業で気がついたのですが、どうも多段階 undo がデフォになって undo の機構が変わった影響か、duplicate 同様、オリジナルの実装のとおり #replace:with:and: だけだとうまく取り消し機能が働かないようです。実際のところ、オーサーイニシャルのペースト機能自体を意識して使うことはほぼないのですが、ペーストのつもりで誤ってこの機構が機能したときに undo できないと腹立たしいので直しておく方が精神衛生上よろしかろうと。


TextEditor >> pasteInitials: aKeyboardEvent
    "Replace the current text selection by an authorship name/date stamp; invoked by cmd-shift-v, easy way to put an authorship stamp in the comments of an editor."

    self insertAndCloseTypeIn.
    self openTypeIn.
    self replace: self selectionInterval with: (Text fromString: Utilities changeStamp) and: [self selectAt: self stopIndex].
    ^ true



▼TrueType フォントの選択可能サイズを追加する機能を追加

エディタとしてはあまり必要ありませんが、将来的にプレゼンツールとして使う場合に備えて。Font Chooser のフォントサイズ枠の右クリックメニューと StrikeFont fromUser のポップアップ中のサイズサブメニューの new size 選択時に機能します。



▼querySymbol:(候補がマルチキーワードセレクタ時)と argAdvance:(コロンの直後)が移動させるキャレット位置の調整

Squeak には Apple Smalltalk 時に実装された alt-q で入力しかけのクラス名、セレクタ、変数名等をシンボルから探して補完する query 機能がまだ残っています。最近、新しい undo/redo システムに対応するため再実装されたのですが、その際に複数のキーワードからなるセレクタの場合であってもキャレットが最後に移動してしまうバグが生じていて使いにくかったので修正しました。

ちなみに query はこんな感じに操作や機能します。

https://www.youtube.com/watch?v=jYOEZVnF9eI


また、この query による補完とコンボで用いると便利な advance という機能(次のコロン+スペースの位置にキャレットをジャンプさせる)もいつの間にかコロン+スペースではなく、コロンの直後にキャレットを移動させるように変更されてしまっていたので元に仕様にリバートしました。

2016-08-29

[] Squeak 5.1 でとりあえず日本語を表示させるために踏んだ手順の記録


Windows 8.1 での手順を記します。

  • Squeak5.1 を http://squeak.org/ の Windows版ボタンをクリックしてダウンロード、展開。
  • http://www.geocities.jp/ep3797/modified_fonts_01.html から komatuna.ttf、komatuna-p.ttf を入手して Squeak5.1-16548-32bit.ja.image と同階層に作った fonts フォルダにコピー。
  • Squeak5.1 を起動。初回起動時の Configure は数が多いので面倒なら Skip(あるいは馴染みの設定だけして Done )。
  • sq51fix_JapaneseLocale-sumim.cs を Squeak のデスクトップにドロップインするなどして install 。
  • Tools → Workspace でワークスペースを開く。
  • Locale switchToID: (LocaleID isoLanguage: 'en'); currentPlatform: (Locale isoLanguage: 'ja') をワークスペースにタイプするかコピペして do it してロケールを変更
  • Apps → Font Importer から Komatuna、Komatuna-P をそれぞれ右クリック→ Link Font 。
  • 次のスクリプトを同じくワークスペースにタイプするかコピペして全選択後 do it 。
| font |
font := StrikeFont familyName: 'Komatuna P' pointSize: 12.
Preferences class selectors
   select: [:sel | (sel beginsWith: 'set') and: [sel endsWith: 'FontTo:']]
   thenDo: [:sel | Preferences perform: sel with: font].
font := StrikeFont familyName: 'Komatuna P' pointSize: 9.
Preferences setPaintBoxButtonFontTo: font.
Preferences setBalloonHelpFontTo: font.
BalloonMorph setBalloonFontTo: font
  • save as... などでイメージを保存。

2016-08-23

[] 『プログラミングElixir』出版記念: Elixir、RubySqueak Smalltalkでspawn/chain.exの速度対決



なぜか Ruby インタプリタ開発者が翻訳をしたことで話題の『プログラミング Elixir』 p.167 にある「14.2 プロセスのオーバヘッド」のサンプルコード


これと似たようなことを Ruby の軽量スレッド(Fiber)と Squeak Smalltalk のプロセスでチャレンジしてみようという試みです。もちろん、Elixir や Erlang のプロセスとはいろいろ違うので、かなり大雑把に似たような処理…ということでご勘弁ください。^^;


ちなみに手元の Elixir では spawn/chain.ex の結果はこのようになりました。

$ elixir -v
Erlang/OTP 19 [erts-8.0] [64-bit] [smp:4:4] [async-threads:10]

Elixir 1.3.1


$ elixir -r spawn-chain.ex -e "Chain.run(10000)"
{62000, "Result is 10000"}


$ elixir -r spawn-chain.ex -e "Chain.run(40000)"
{156000, "Result is 40000"}


$ elixir -r spawn-chain.ex -e "Chain.run(100000)"
{438000, "Result is 100000"}


$ elixir -r spawn-chain.ex -e "Chain.run(400000)"
12:31:03.936 [error] Too many processes
** (SystemLimitError) a system limit has been reached


$ elixir --erl "+P 1000000" -r spawn-chain.ex -e "Chain.run(400000)"
{1609000, "Result is 400000"}


$ elixir --erl "+P 1000000" -r spawn-chain.ex -e "Chain.run(1000000)"
{4344000, "Result is 1000000"}

4万で 156ミリ秒 、40万で 1.61秒、100万で 4.34秒とはさすがです。



▼ Squeak Smalltalk 版

残念ながら Squeak/Pharo には Io のようなアクター(あるいはメッセージの非同期通信)機能は組み込みではない上、プロセス(通常の言語でいうところのスレッド)についても resume の際に後述の Ruby の Fiber のように引数を与えることができないため、 Elixir の spawn/chain.ex の動きをストレートには再現できません。

そこで、SharedQueue(next を受け取ると、他のプロセスから nextPut: 等でエレメントがプッシュされるまでアクティブプロセスを停止)をメッセージキューに見立てたアクターっぽい機構で似たような動きを再現してみました。なお、Squeak/Pharo ではブロック(無名関数。[] で処理を括ったもの)に fork というメッセージを送ると、アクティブプロセスと同じ優先度で新しいプロセスが立ち上がるしくみになっています(今回は使いませんが、優先度を変更するには forkAt: を用います。ちなみに優先度が違うプロセス同士の並行処理はノンプリエンプティブっぽく振る舞います)。


| N time ans |
N := 40000.
Smalltalk garbageCollect.
time := [
   | me last |
   me := SharedQueue new.
   last := (1 to: N) inject: me into: [:sendTo :dummy |
      | mbox |
      mbox := SharedQueue new.
      [sendTo nextPut: mbox next + 1] fork.
      mbox
   ].
   last nextPut: 0.
   ans := me next
] timeToRun milliSeconds.
^{time. 'Result is ', ans printString}
=> {382 . 'Result is 40000'}
=> {3526 . 'Result is 400000'}

結果は、4万で 382ミリ秒、40万で 3.53秒(Squeak5.0 で計測。Squeak では Workspace、Pharo なら Playground へのコピペ → print it で動作します)。もちろん、Squeak/Pharo Smalltalk のプロセスは Elixir のそれと比べて限定的なので、あくまで参考値ではありますが、よく健闘してます。しかし試してみたところ 70万プロセスになると VM が落ちます。無念。



▼ Ruby 版

Squeak/Pharo のプロセスとは違い Ruby の軽量スレッドである Fiber は、引数を受け取ることができます(今回は使いませんでしたが返値も返せます)。そこで Ruby 向けには Squeak/Pharo とは別のアプローチで spawn/chain.ex と似たような動きになる処理を書いてみました。なお、計時結果の数値の単位は秒です。

$ cat spawn-chain.rb
require 'benchmark'
require 'fiber'

n = ARGV[0].to_i
n = 10 if n == 0
ans = 0
time = Benchmark.realtime{
  me = Fiber.new{ |n| ans = n }
  last = (1..n).reduce(me){ |send_to,_|
    Fiber.new{ |n| send_to.transfer(n + 1) }
  }
  last.resume(0)
}
p [time, "Result is #{ans}"]


$ ruby -v
ruby 2.4.0dev (2016-08-22 trunk 55983) [x86_64-cygwin]


$ ruby spawn-chain.rb 40000
[22.913834010018036, "Result is 40000"]

結果は 4万で 23秒とふるわず。ちなみに 5万以上ではエラーで計測不能でした。Ruby3 での高速化に期待したいところです。

 
2004 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 08 | 10 | 12 |
2013 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 11 | 12 |
2014 | 01 | 02 | 05 | 07 | 08 | 09 | 10 | 11 |
2015 | 04 | 07 | 08 | 11 | 12 |
2016 | 02 | 03 | 06 | 07 | 08 |
2017 | 04 | 05 |

最近のコメント

1. 06/25 sumim
2. 06/25 山田
3. 08/29 squeaker
4. 08/29 ardbeg1958
5. 10/16 umejava

最近のトラックバック

1. 05/25 プラグインレスでSVGを表示する「SIE」開発ブログ - メッセージをや...
2. 01/30 no_orz_no_life - Erlangとジャンケン
3. 12/31 檜山正幸のキマイラ飼育記 - J言語、だってぇー?
4. 09/04 Twitter / @atsushifx
5. 07/06 みねこあ - オブジェクト指向 と FizzBuzz

この日記のはてなブックマーク数
1673636