DCI パラダイムの提唱者、レインスカウ氏の BabyIDE で遊ぶ
BabyIDE は、古くは MVC(Model-View-Controller)、近年では DCI(Data-Context-Interaction)というパラダイムの提唱者として知られるトリグヴ・レインスカウ(Trygve Reenskaug)氏自らが手がける Squeak Smalltalk ベースの DCI 向けプログラミング&モデリングのためのツールで、彼の考える BabyUML(UML や他の言語の構成概念を基に作られたプログラミング規律)のための実験の場でもあります。
BabyIDE is an interactive development environment that supports the DCI paradigm with specialized browsers for each perspective. These browsers are placed in overlays within a common window so that the programmer can switch quickly between them. DCI with BabyIDE marks a new departure for object oriented programming technology.
Trygve/DCI
ちなみに BabyIDE とか BabyUML に付いている Baby は、1948年にマンチェスター大で作られた世界初のノイマン型(プログラム内蔵型)コンピューターの一つである SSEM という試作機のニックネームであった「The Baby」がその由来らしいです。このプロジェクトの成果が赤ん坊がそうであるように、自分の足で立てるまでには成熟したものではない一方で伸びしろも有することを示すとと共に、BabyUML が仮想的なプログラム内蔵型コンピューターのようなものをターゲットにしていることともリンクさせているようです。
The world's first digital stored program computer was the Manchester Small Scale Experimental Machine―“The Baby”. This Baby was small, it was designed for testing the Williams-Kilburn cathode ray tube high speed storage device. It was a truly minimal computer with an operations repertoire of just 7 instructions. It executed its first program on 21st June 1948. The machine was insignificant in itself, but it marked the beginning of a new era.
The BabyUML project has created what may be the world's first integrated development environment based on a truly object oriented programming paradigm (Simula, Smalltalk, C++, Java, and others are based on the class paradigm. Even self code descibes one object at the time; there are no facilities for describing networks of collaborating objects). The result of the BabyUML project is like a new born baby. Its functionality is extremely limited. It cannot stand on its own two feet, but there is room for almost unlimited growth. My dream is that many people will adopt the Baby ideas and create their own vigorous variants.
http://folk.uio.no/trygver/2008/commonsense.pdf
I have somewhat whimsically chosen the name BabyUML. The English Baby was the world’s first stored program computer while the target for BabyUML is a virtual, stored program object computer that spans one or more hardware computers.
The BabyUML discipline of programming
後者については、ジム・コプリエン氏の「間」の概念への興味からも、アラン・ケイの“メッセージングのオブジェクト指向”において、オブジェクトが高速のネットワークで互いに接続された小さなコンピューターである、と例えられることとも無関係ではないように感じます。
Smalltalk object is a recursion on the entire possibilities of the computer. Thus its semantics are a bit like having thousands and thousands of computer all hooked together by a very fast network.
The Early History of Smalltalk
The big idea is "messaging" - that is what the kernal of Smalltalk/Squeak is all about (and it's something that was never quite completed in our Xerox PARC phase). The Japanese have a small word - ma - for "that which is in between" - perhaps the nearest English equivalent is "interstitial".
http://c2.com/cgi/wiki?AlanKayOnMessaging
さて前置きはこのくらいにして、この BabyIDE を使い、id:digitalsoul さんによる Groovy および Scala によるローンシンジゲートの例を Squeak Smalltalk で書いてみます。なお、以下のコードで記述された仕様は Ruby札幌勉強会でのデモ向けのため、元エントリーのものに比べてかなり簡素化されているので、どうぞあしからず。
注意: 以下のチュートリアル部分の記述は比較的新しいバージョンの BabyIDE に対応していません。インストールの仕方からコードや操作の記述などを修正した改訂版がありますので こちら をご参照ください。
▼ BabyIDE のインストールと起動
Win 向けに動作に必要なファイル一式のアーカイブが公開されています。他の OS でも、その OS 向けの Squeak VM を別途用意すれば動作するはずです。
Trygve/BabyIDE から BabyIDE-1.ZIP を(このチュートリアルで使用したバージョンがその後、入手できなくなったようなので こちら) をダウンロード- 適当な場所に展開
- .image ファイルを Squeak.exe(Sqeuak VM)にドロップイン
▼ BB1クラスブラウザを開く
BabyIDE というだけあって、専用のクラスブラウザが用意されています。この Squeak イメージでは、ウインドウを開く操作の際には、矩形領域をしていないといけないようになっているので注意が必要です。使いづらければ、 SystemWindow>>#openInWorld: を versions からひとつ前のバージョンに revert すれば、通常通りの動きになります。
- デスクトップメニュー → open... → BabyIDE... → BBLoanSyndicate と入力して Accept (alt + s)
- 開きたいウインドウの位置と大きさを、左上から右下に向かってドラッグすることで指定(前述の revert 後は不要)
▼ データの定義
Squeak Smalltalk でクラスの定義をしたことがあれば、説明は不要かと思います。
- コード枠(下段)にあるクラス定義テンプレートの NameOfSubclass のところを BBFacility に書き換え
- instanceVariableNames: の引数である '' を 'limit loan shares' に書き換え
- accept (alt + s) でコンパイル → BBFacility が左上のクラス名リスト枠(上段左端)に現われる
Object subclass: #BBFacility instanceVariableNames: 'limit loan shares' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Data'
引き続き、BBFacility の定義を書き換えるかたちで BBLoan を定義します。
- クラス名を BBLoan に変更し、インスタンス変数の limit loan を削除して shares のみ残し accept (alt + s)。
Object subclass: #BBLoan instanceVariableNames: 'shares' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Data'
続いて、それぞれのクラスに暗黙に呼ばれる初期化メソッド(#initialize)を定義します。
- クラス名リスト枠(上段右端)で BBFacility クラスをクリックして選択
- メソッドカテゴリーリスト枠(上段右から三番目)で no messages をクリック
- 次のメソッドを入力し、accept (alt + s) でコンパイル。イニシャルを尋ねられたら適当に入力して Accept (alt+s)。
initialize
shares := Dictionary new
同様に BBLoan にも同じメソッドを定義します。
これでデータの定義は完了です。なお、Share、SharePie、Company などはコードの簡素化のため省きます。
▼ コンテキストの定義
データ同様、コンテキストオブジェクトのクラスをデータの時と同様の操作で定義します。
- Context タブをクリックして切り替え
- NameOfSubclass を BBLoanSyndicate に書き換え
- instanceVariableNames: の引数である '' を 'facility' に書き換え
- accept (alt + s) → BBLoanSyndicate が左上のクラス名リスト(上段左端の枠)に現われるのを確認
BB1Context subclass: #BBLoanSyndicate instanceVariableNames: 'facility' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Context'
▼ データおよびコンテキストへのアクセッサーの追加
以下では簡単のため、データおよびコンテキストのインスタンス変数に対するアクセッサーメソッドが定義されていることを前提にしているので、このタイミングでこれらを自動生成しておきます。ワークスペース(デスクトップメニュー → open... → workspace)などで、次のコードを入力後に選択し、 do it (alt + d) してください。
| browser | browser := Browser new. {BBLoanSyndicate. BBFacility. BBLoan} do: [:class | browser setClass: class selector: nil; createInstVarAccessors]
▼ インタラクションの定義
コンテキストを指定して、そこでのロールとその振る舞いを定義します。ここまでの Smalltalk の伝統的なクラスブラウザの作法でのクラス定義と違い、ロールは楕円で示され、それらを矢印でつなぐことで相互関係を図形的に記述します。
- Interaction タブをクリックして切り替え
- BBLoanSyndicate コンテキストをクリックして選択
- 右上の枠で右クリックして add role → Lender と入力 → Accept (alt + s)
- Lender ロールを示す楕円を枠内の適当な場所をクリックして配置
- 同様に、AmountPie、PercentagePie を作成して配置
- Lender を右クリックして add link → AmountPie をクリック
- AmountPie を右クリックして add link → PercentagePie をクリック
処理を簡潔に書きやすくするため、元エントリーとは関係をちょっと変えています。
▼ ロールの定義
各ロールにメソッドを定義します。
- ロールをクリックして選択 → 下のコード枠にメソッドを記述 → alt + s で accept
定義するメソッドを次に示します。入力やコピペは冒頭の ロール名 >> を省いて行なってください。accept 時に、そのロールに対する最初のメソッド定義の場合は、「Role trait for Lender is not defined. Do you want to define it so that I can compile your method?」と尋ねられるので、Yes としてください。また、以下のコードにはいくつか未定義のメソッドが含まれています。コンパイラにその旨を警告されたときは、未定義のメソッド名をそのままポップアップから選択してください。
Lender >> draw: amount AmountPie increase: amount. self limit: self limit - amount
Lender >> pay: amount AmountPie decrease: amount. self limit: self limit + amount
AmountPie >> increase: amount PercentagePie shares associationsDo: [:assoc | | company percentage | company := assoc key. percentage := assoc value / 100 asFloat. self shares at: company put: (self shares at: company) + (amount * percentage)]
AmountPie >> decrease: amount | shares total | shares := self shares. total := shares sum asFloat. shares keysDo: [:company | | current | current := shares at: company. shares at: company put: current - (amount * (current / total))]
▼ ロールのデータへのバインド
- Lender を右クリック → connect using class → BBFacility を選択
- AmountPie を右クリック → connect using class → BBLoan を選択
▼ コンテキストにおけるロールへのデータマッピングメソッドの定義
- Context タブをクリックして切り替え
- BBLoanSyndicate をクリックして選択解除後、ふたたびクリックして選択
- role binding カテゴリーがメソッドカテゴリーリスト(左から三番目の枠)に追加されていることを確認
- role binding カテゴリーをクリックして選択
- メソッド名リスト(右端の枠)から Lender を選択し、コード枠内のコードを次のように書き換えて accept (alt + s)。
Lender ^facility
- 同様に AmountPie、PercentagePie も定義を次のように書き換え、accept (alt + s)。
AmountPie ^facility loan
PercentagePie ^facility
▼ コンテキストへの各種メソッドの定義
BBLoanSyndicate に初期化メソッド(#buildFacility、#joinFacility:percentage:)、および、トリガーメソッド(#draw: #pay:)を定義します。
- カテゴリーリスト枠(上段三番目)で role binding が選択されていたらクリックして選択解除
- 同じくカテゴリーリスト枠で右クリック → add category... → そのまま Accept (alt + s)。
- 同じくカテゴリーリスト枠に triggers が現われるのでクリックして選択
- 次のそれぞれのメソッド定義を下段コード枠にペーストして accept (alt + s)。
buildFacility facility := BBFacility new. facility loan: BBLoan new.
joinFacility: company percentage: int facility shares at: company put: int. facility loan shares at: company put: 0
draw: amount self executeInContext: [(self at: #Lender) draw: amount]
pay: amount self executeInContext: [(self at: #Lender) pay: amount]
▼ 動作チェック
以上で定義は完了です。実際に動作を確認してみましょう。アクセッサーの定義と同様にワークスペースなどで次のコードを入力後、選択して、do it (alt + d) すると、トランスクリプトが開いて結果が出力されます。
| syndicate | syndicate := BBLoanSyndicate new buildFacility. syndicate joinFacility: #A percentage: 50. syndicate joinFacility: #B percentage: 30. syndicate joinFacility: #C percentage: 20. syndicate facility limit: 100000. syndicate draw: 10000. syndicate pay: 5000. World findATranscript: nil. syndicate facility loan shares associations sort do: [:assoc | Transcript cr; show: assoc]
[2669]#A->2500.0 [3763]#B->1500.0 [1129]#C->1000.0
と、まあ、いちおう動きはするのですが、じつはまだ #executeInContext: をコンテキスト内のメソッドで書かなければならない理由や self at: #Role を self Role と書いてはいけない理由などモヤっとしているところも多々あるので、DCI デザインを含め、まだまだ勉強が必要なことを痛感しました。^^;