DCI パラダイムの提唱者、レインスカウ氏の BabyIDE で遊ぶ 改訂版
BabyIDE については元エントリーのこちらの長い前振りを参照してください。
さらに比較的新しいバージョンの BabyIDE を念頭に、インストールの仕方からコードや操作の記述などを修正した改訂版がありますので こちら もご参照ください。
BabyIDE のバージョンがいまひとつはっきりしていなくて、まじめにチェックしていなかったのですが、久しぶりに遊んでみたら、どうやら現在配布されている BabyIDE ではくだんのエントリーの通りにしてもうまく組めないことも判明したので、現在配布されているバージョン向けに操作を(ほとんどは同じで、ほんの一部ですが)書き直してみました。
[追記 2013-01-23] この改訂版を書いたすぐあとに BabyIDE を含む DCI の新しいポータルサイトの存在をしり、さらにもう少し新しいバージョンの BabyIDE(BabyIDE-2.ZIP)が入手可能であることなどが判明したので、その後、再々度ちょこちょこと修正を試みています。あしからず。
組んでいるのはこちらのローンシンジケートの例
をかなり端折ったものです。内容としては複数の会社が共同で出資して金を貸し出したり、その一部が返済された場合に、各社の負担額はどのように変化するかというものです。
DCI の D、つまりデータとして、シンジケートを表わす BBFacility、各社の出資状況を保持する BBLoan、次の融資もしくは返済時の分担割合を保持する BBShare を用意(なお、BabyIDE では実クラス名に BB 〜たぶん BaBy の略〜 を冠することになっているようなのでこのエントリーでもそれに従います)。BBLoanSyndicateコンテキストにおける、ロールとして融資・返済の窓口を担う Lender、各社の出資状況を知る AmountPie、各社の分担比率を知る PercentagePie が想定されています。
▼ BabyIDE のインストールと起動
Win 向けに動作に必要なファイル一式のアーカイブが公開されています。他の OS でも、その OS 向けの Squeak VM を別途用意すれば動作するはずです。BabyIDE-2 が組みこまれている 7179-basic.83.image は Squeak3.10.2 のイメージなので、その頃に配布されていた VM を ftp.squeak.org より入手するのがよいと思います(たとえば Mac なら ftp://ftp.squeak.org/3.10/mac/Squeak3.10.2-7179mac.zip とか)。
- DCI Downloads から BabyIDE-2.ZIP をダウンロード
- 適当な場所に展開
- 7179-basic.83.image を Squeak.exe(またはお使いのOS向けの Sqeuak VM)にドロップイン
注意:起動直後は Shapes(BB2Shapes)のデモが走っているため、操作が重たくマシンの性能によってはほとんどマウス操作を受け付けてくれないことがあります。このアニメーションは右クリックでいったん停止させることができるので、そうしてください。なお、この Shapes のデモはマウスの中ボタン(スクロールボタン。あれば)クリックか、alt/cmd キーを押しながらの右クリックで選択し(ハローと呼ばれる小さなボタン群によって囲まれます)、左上にあるピンク色のクローズハロー(×ボタン)をクリックすることで消すことができます(かならずアニメーションが停止している状態で削除してください)。デスクトップクリック → open... → BB2Shapes animation でいつでも呼び出せますので消しても大丈夫です。この状態で デスクトップクリック → save しておけば次回起動時には表示されなくなります。
▼ BB1クラスブラウザを開く
Baby“IDE”というだけあって、DCI アーキテクチャに特化した専用のクラスブラウザが用意されています(まあお手製なので、組み込みのものに比べてしまうと、いろいろと物足りない部分も多々ありますが…)。
ところで、この Squeak イメージでは、ウインドウを開く操作の際には、矩形領域をしていないといけないようになっている(おそらく、レインスカウさんが PARC で使われていた頃の Smalltalk の動きに似せるようにした改造でしょう)ので注意が必要です。使いづらければ、 SystemWindow>>#openInWorld: を versions からひとつ前のバージョンに revert すれば、通常通りの動きになります。
加えて、メニューなどの字が小さくて読みにくい場合は、デスクトップクリック → appearance... → system fonts... → demo mode を選んでおくと、少し読みやすくなるかもしれません。ここらへんは Squeak を使うときの通常のスタンスで、どんどん使いやすいように変えてしまうのが吉です。
ではローンシンジケートの例を DCI で組むためのクラスブラウザを開きましょう。一般的な IDE における「プロジェクトの作成」みたいなものですね。
- デスクトップクリック → open... → BabyIDE1... → other... → 出てきた入力欄に BBLoanSyndicate と入力して Accept (alt/cmd + s)
- 開きたいウインドウの位置と大きさを、左上から右下に向かってドラッグすることで指定(前述の revert 後は不要ですが、デフォルトのウインドウの大きさが小さいときは四隅のどれかをドラッグして大きさを適当に整えてください)
▼ データの定義
まず DCI の D 。データ用のクラス(BBFacility、BBLoan。元エントリーの Share、SharePie、Company はコードの簡素化のため省略)を定義します。DCI のデータには何の細工もいらないので普通のクラス定義です。Squeak Smalltalk の通常のクラスブラウザでクラスの定義をしたことがあれば、説明はほぼ不要かと思います。
- コード枠(下段)にあるクラス定義テンプレートの NameOfSubclass のところを BBFacility に書き換え
- instanceVariableNames: の引数である '' を 'limit loan shares' に書き換え
- accept (alt/cmd + s) でコンパイル → BBFacility が左上のクラス名リスト枠(上段左端)に現われる
Object subclass: #BBFacility instanceVariableNames: 'limit loan shares' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Data'
引き続き、BBFacility の定義を書き換えるかたちで BBLoan を定義します。
- クラス名を BBLoan に変更し、インスタンス変数の limit loan を削除して shares のみ残し accept (alt/cmd + s)。
Object subclass: #BBLoan instanceVariableNames: 'shares' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Data'
続いて、それぞれのクラスに暗黙に呼ばれる初期化メソッド(#initialize)を定義します。
- クラス名リスト枠(上段右端)で BBFacility クラスをクリックして選択
- メソッドカテゴリーリスト枠(上段右から三番目)で no messages をクリック
- 次のメソッドを入力し、accept (alt/cmd + s) でコンパイル。イニシャルを尋ねられたら適当に入力して Accept (alt/cmd + s)。
initialize
shares := Dictionary new
同様に BBLoan にも同じメソッドを定義します。
これでデータの定義は完了です。
▼ コンテキストの定義
次は DCI の C 。コンテキスト用のクラス BBLoanSyndicate を定義します。クラスを定義するところは、データのクラスの時と同じです。
- Context タブをクリックして切り替え
- NameOfSubclass を BBLoanSyndicate に書き換え
- instanceVariableNames: の引数である '' を 'facility' に書き換え
- accept (alt/cmd + s) → BBLoanSyndicate が左上のクラス名リスト(上段左端の枠)に現われるのを確認
BB1Context subclass: #BBLoanSyndicate instanceVariableNames: 'facility' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Context'
(元エントリーにひっぱられてうっかり BBLoanSyndicate という名前にしてしまいましたが、ここはもう少し考えてこのコンテキストの内容を反映した名前にしたほうがよかったかも。そうでなくとも、せめて BabyIDE の他の例に準ずるかたちで最後に Ctx を付すくらいはすべきでした。orz )
▼ データおよびコンテキストへのアクセッサーの一括定義
以下では簡単のため、データおよびコンテキストのインスタンス変数に対するアクセッサーメソッドが定義されていることを前提にしているので、このタイミングでこれらを自動生成しておきます。
適宜、タブを切り替えて、Dataタブの BBFacility、BBLoan、Contextタブの BBLoanSyndicate をクリックして選択してそのままマウスポインタを枠から出ないようにして(枠の中で)右クリック → create inst var accessors を選択します。
もし右クリックでメニューが出ずにウインドウが選択されて(ハローと呼ばれる小さなカラフルな丸いハンドラで囲まれて)しまったら、ワークスペースを開いて次の式を do it (alt/cmd + d) して設定を切り替えてみてください。
InputSensor swapMouseButtons: Preferences swapMouseButtons not
うまくいかないときは、第一ペインのスクロールバー最上部にあるボタンをクリックしても右クリックメニューを呼び出すことができます。
▼ コンテキスト内のロール作成とロール依存関係
コンテキストに参加するロールとそのインタラクション(メソッド)を定義します。
まずロールとそれら相互の依存関係を定義します。ここまでの Smalltalk の伝統的なクラスブラウザの作法でのクラス定義と違い、楕円で示されたロールを作り、依存関係はそれらを矢印でつなぐことで図的に記述します。
- Interaction タブをクリックして切り替え
- 左上枠のBBLoanSyndicate コンテキストをクリックして選択
- 右上の枠で右クリックして add role → Lender と入力 → Accept (alt/cmd + s)
- Lender ロールを示す楕円を枠内の適当な場所をクリックして配置
- 同様に、AmountPie、PercentagePie を作成して配置
- Lender を右クリックして add link → AmountPie をクリック
- AmountPie を右クリックして add link → PercentagePie をクリック
コードを簡潔にするため、ローンシンジケートの例の元エントリーとはいろいろと変えてしまっています。あしからず。
▼ インタラクション(ロールメソッド)の定義
そして DCI の I 、各ロールにインタラクション(ロールのメソッド)を定義します。
- ロールをクリックして選択 → 下のコード枠に次に示したメソッドを入力するかコピペ → alt/cmd + s で accept
定義するメソッドは4つ(Lender の #draw: と #pay: 、AmountPie の #increase: と #decrease:)ですが、冒頭の「ロール名 >> 」より後がメソッドのコード本体なので、そこだけコピペ(あるいはタイプして入力)して「ロール名 >> 」の部分は含めないでください。Smalltalk 界隈では複数の異なるクラスに属するメソッドのコードを列挙して示す必要がある際に、そのメソッドがどのクラスのものかが区別できるようにそれぞれの頭に「クラス名 >> 」と書く慣習があります。
たとえば Lender の #draw: であれば、Lender >> の直後の draw: amount から3行目の limit - amount の行までをタイプして入力するか、選択してコピペしてください。その後の accept(alt/cmd + s) もお忘れなく。ちなみに、コード枠での入力や変更後の accept 操作は通常の言語処理系におけるコンパイルに相当します。なお、バイトコードへのコンパイルはクラス単位ではなくメソッドごとに随時行なうのが Smalltalk の開発スタイルです。
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))]
▼ コンテキストにおけるロールへのデータマッピングメソッドの定義
コンテキストにおいて、どのロールをどんなオブジェクトに演じさせるのかを割り振るメソッドを定義しておく必要があります。
- ロールを指さないように下地の部分でクリック → role binding methods
- ブラウザが開くので、三つのデータマッピングメソッドがリストされていることを確認。
- Lender を選択し、コード枠内のコードを次のように書き換えて accept (alt/cmd + s)。
Lender ^facility
- 同様に AmountPie、PercentagePie も定義を次のように書き換えて accept (alt/cmd + s)。
AmountPie ^facility loan
PercentagePie ^facility
各ロールへのデータマッピングメソッドの定義が終わったら Message Category Browser (BBLoanSyndicate) ウインドウは綴じてしまって構いません。
なお BabyIDE の場合、ロールの割り振りに相互依存性があったりしてその順序が重要な場合には、コンテキストの #remap をオーバーライドしてデータマッピングメソッドのコール順を明示的にしておく必要があります。しかし、この例ではその必要はなさそうです。
▼ コンテキストへの各種メソッドの定義
BBLoanSyndicate に初期化メソッド(#buildFacility、#joinFacility:percentage:)、および、トリガーメソッド(#draw: #pay:)を定義します。
- Context タブをクリックして切り替え
- 第三枠(カテゴリーリスト枠)で右クリック → new category... → initialization と入力してから Accept (alt/cmd + s)。
- 追加された initialization をクリックして選択。
- 次のそれぞれのメソッド定義を下段コード枠にペーストして accept (alt/cmd + 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
- 第三枠(カテゴリーリスト枠)で右クリック → new category... → triggers と入力してから Accept (alt/cmd + s)。
- initialization をクリックして選択解除後、triggers をクリックして選択。
- 次のそれぞれのメソッド定義を下段コード枠にペーストして accept (alt/cmd + s)。
draw: amount self triggerInteractionFrom: #Lender with: #draw: andArgs: {amount}
pay: amount self triggerInteractionFrom: #Lender with: #pay: andArgs: {amount}
▼ 動作チェック
以上で定義は完了です。実際に動作を確認してみましょう。新しく Testing というタブを作り、そこに動作確認用の適当なクラスを定義します。テストを書いてもいいかもしれません。
- タブ枠を右クリックして new perspective... を選択、Testing と入力して Accept (alt/cmd + s)
- データやコンテキストのときの要領で BBLoanSyndicateTesting クラスを定義。
- クラスリスト枠(第二枠)下にある class ボタンをクリックして選択し、クラスメソッドサイドに切り替え。
- メソッドカテゴリペイン(第三枠)から no messages をクリックして選択してから、次のコードを入力するかコピペして accept(alt/cmd + s)。
example "BBLoanSyndicateTesting example" | 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]
コメントアウトしてある BBLoanSyndicateTesting example 部分を選択して、do it (alt/cmd + d) すると、トランスクリプトが開いて結果が出力されます。
[2669]#A->2500.0 [3763]#B->1500.0 [1129]#C->1000.0
タブ枠を右クリック → fileOut this App でファイルアウトできるはずなのですが、うまく動かない(はき出せるが読み込めない)ようなのでパッチを書きました。デスクトップにドロップイン→ install into new change set するなどして読み込んでから前述 fileOut thsi App 機能を使ってみてください。たぶん読み込める .st を作ってくれるはずです。
ここまでの作業でできるコードを試しに fileOut this App してみたファイルがこちらの BBLoanSyndicate.st です。BabyIDE-2.ZIP から展開直後のイメージにファイルインすれば BBLoanSyndicateTesting example が動かせるはずです。とりあえずコードの動作だけ確認したい場合などにお試しください。