DCI パラダイムの提唱者、Reenskaug 氏の BabyIDE で遊ぶ - Squeak4.5対応版向け
久しぶりに DCI がらみで BabyIDE にヒントがあるかもしれないな…と思われる話題に接したのを機に、以前遊んでみた BabyIDE のその後を調べてみたところ、引き続き Squeak3.10.2 上で開発が続けられている BabyIDE-2018.07.31.ZIP というのを発見して驚きました(氏は御年88歳!)。同時に、それよりちょっとバージョンは古そうですが、プラットフォームとしては比較的最近の Squeak4.5 にインストールして使えそうな SqueakMap パッケージ BabyIdeAllInOne の存在を知ることもできたので、今回はこれで遊んでみることにしました。
DCI と BabyIDE については過去の記事を参考にしてください。
BabyIDE もかなり癖のある処理系・IDE ですが、それを実装している Squeak Smalltalk という処理系・IDE も通常の言語からするとずいぶん風変わりなものなので、目を慣らすために次のチュートリアルをざっと眺めたり、できれば実際に Squeak を(チュートリアルに合わせて http://ftp.squeak.org/3.9/ から必要なファイルを得て 3.9 を)インストールして、実際に体験してみるのも一興かと思います。
Smalltalk 処理系は Squeak 以外にもいくつかありますので、こちらも参考まで。
Squeak4.5の入手と起動
Squeak Smalltalk の公式ページから今ダウンロードできるのは最新版の 5.2 ですが、BabyIdeAllInOne パッケージが対象にするそれより少し前の 4.5 は https://ftp.squeak.org/ から Squeak-4.5-All-in-One.zip として入手可能です。
これをダウンロードしてから適当な場所に展開し、Windows は squeak.bat、Mac なら Squeak-4.5-All-in-One.app のダブルクリックで、Linux は squeak.sh で起動できるはずです。ただ少し古いバージョンなのでOSの更新状況によってはうまく動かないかもしれません。適宜対処してください。^^;
[追記]macOS Catalinaで古いSqueakが動かせなくなったとは聞いていて、いつものセキュリティ周り強化の余波だろうと軽く考えていたのですが、今回のは文字通り古い32bitソフトの息の根を止める処置なので致命的でした^^; なんとか動かせないか2つほど方法を考えました。
ひとつは順当に仮想化技術を使う方法です。見よう見まねでDockerイメージを作ってみましたのでお試しください 。→https://hub.docker.com/repository/docker/sumim/babyide14-docker
もうひとつは squeak.js.org を使う方法で、Launcherの点線枠に .image と .changes、.sources を含むファイル一式(VM関連以外の─)をドロップインするとあっさり起動します。Chrome推奨です。ただ squeak.js.org のデモ自体がいつまで使えるか分からないので、早めにこれ自体をローカルで動かせるように動態保存しておく方がよいかもしれません(→GitHub - codefrau/SqueakJS: A Squeak VM in Javascript)。[/追記]
ともあれ、ダウンロードして展開するだけ(古いバージョンだと、最低限必要なファイルを知って揃える必要はありますが…)という手軽さと環境を汚すことなくインストール(不要になれば削除でアンインストール)できる点は Squeak Smalltalk の非常に良いところなので、風変わりだと食わず嫌いをせず、ぜひ気軽に試してみていただきたいです。w
BabyIdeAllInOne-1.4 のインストール
Squea4.5 の起動後、Apps → SqueakMap Categories (もしくは、デスクトップクリックでポップアップするメニュー → open... → SqueakMap Categories)で Categorical SqueakMap Package Loader を起動し、まず Update します。
パッケージリストがうまく更新されると左側のペインにカテゴリーリストが表示されるので、Development tools を選択し、続いて右のペインから BabyIDE (->1-2.9) ではなく、BabyIdeAllInOne の (1.4) を選択し Install します。
Transcriptウインドウが開いてコメントや警告がいろいろと出ますが、サブパッケージのロードの失敗( ERROR: Could not download version BB8aMoveShape-TRee.3 from http://www.squeaksource.com/DCI )のような致命的なものでなければ大丈夫です。
Transcriptで警告をスルーしつつスクロールして遡ると
========== Planning tool BB9Planning-TRee.5 ==========
Prokon: An activity network planning program
See BB9DBase comment.
のようにサンプルプロジェクトの簡単なコメントを読むことができます。この手の情報はソースから得られるので気にしなくてよいかと思いますが、Transcript は以降、結果出力表示( Transcript cr show: 'hoge'
)等にも利用するのでそのままにしておきます。Categorical SqueakMap Package Loader の方はもう不要なので閉じてしまいます。
次回起動時以降、この状態から始められるようにネズミ(Squeakアイコン)メニューから Save Image を選んで環境を保存しておきましょう。
この手のネット経由のインストールは時間が経つとうまく動作しなくなるのが常なので、動くうちにとりあえずインストールだけでも済ませておいて、この環境まるごと保存機能で“動態保存”しておくのがよさそうです。
組込みサンプル(DCIアプリケーション)の定義ブラウズと実行
組込みのサンプル(DCIアプリケーション)は Apps メニュー → BB1: IDE → Select Application メニューにリストされた BB2 以降を選ぶと babyIDEウインドウを開いて定義を閲覧・編集することができます。
コンテクスト(Context)とデータモデル(Data)のブラウザは通常のSmalltalkのクラスブラウザに似た感じで使えます。
コンテクストブラウザの SWAP TO INTERACTION ボタンでロールブラウザに切り替えられます。
左上にコンテクストがリストされるのでどれかを選ぶと、右側のペインにロールがダイアグラムで表示されます。メソッド付きのロール(太枠で表示)をクリックして選択すると、左下のペインに定義されたメソッドがリストされるので、クリックすると右側ペインに定義が表示されます。ロールの上でマウスポインタをホバーさせると、バインディングが吹き出しで表示されます。
DCIアプリケーションの起動時は、同じく Apps メニューから(前述の BB1: IDE のサブメニューを経由せず、直接)BB2 以降のアプリケーション名を選択します。ちなみに、BB2Shapes は右クリックでアニメーション停止後、再度右クリック→ EXIT で終了できます。
各アプリケーションの意義やそれを通じて示したいことについては DCI Documentation - BabyIDE examples などのドキュメントに書かれています。かならずしもGUIのあるアプリケーションばかりではないですし、GUIがあってもあまり直感的な実装にはなっていないので、試しに走らせる前にどんなアプリケーションなのかは知っておく方がよいでしょう。
ロールがうまく定義されないバグの修正
コンテクストに新しいロールを定義したときに、内部的に対応するクラスが自動生成されるカラクリがうまく動かないようだったので、いろいろ定義して遊んでみる前にこれを修正しておきます。
まず、Tools メニューから Workspace を選んでワークスペース(コードを実行できる書き捨てのメモ帳)を開きましょう。ウインドウ内にマウスポインタを移動してから「ensurerole」などと入力し、alt + q (Mac では cmd + q。以下同様)とタイプしすると「ensureRoleClassFromRoleName: notifying:」とメソッド名が補完されるので(うまくできないときはここからコピペするかタイプして入力してください)、続けて alt + m とタイプする(もしくは右クリック → more... → implementors of it を選択する)とこのメソッドの定義が表示されます。
「, '_'」の部分を削除して alt + s(もしくは、右クリック → accept )でコンパイルすれば作業は終了です。初回のコンパイル時にイニシャルを尋ねられると思うので、適当に答えてやってください。
なお、ここで入力したイニシャルやニックネームは versions ボタンを押して表示することができるメソッドの変更履歴の管理などに使われます(たとえば、変更前の Trygve Reenskaug 氏記述のバージョンをクリックして選択後、revert ボタン(あるいは、右クリック→ revert to selected version )でいつでもこの変更を取り消すことができます)。
[変更] 修正方法を変えました。すでに , '_' の削除で対処済みの場合は、早速ですが^^; ensureRoleClassFromRoleName: notifying: メソッドの定義を先述の方法で呼び出し、versions → 修正前の Trygve 版選択 → revert で元に戻してから以下を続行してください。
ウインドウ内にマウスポインタを移動してから「createrole」などと入力し、alt + q (Mac では cmd + q。以下同様)とタイプしすると「createRoleClass: roleName: notifying:」とメソッド名が補完されるので(うまくできないときはここからコピペするかタイプして入力してください)、続けて alt + m とタイプする(もしくは右クリック → more... → implementors of it を選択する)とこのメソッドの定義が表示されます。
「roleClassName」に続けて「 asString storeString」を追加して alt + s(もしくは、右クリック → accept )でコンパイルすれば作業は終了です。初回のコンパイル時にイニシャルを尋ねられると思うので、適当に答えて Accept してください。
なお、ここで入力したイニシャル(ニックネームやファーストネームでも何でもよい)は versions ボタンを押して表示することができるメソッドの変更履歴の管理などに使われます。たとえば、変更前の Trygve Reenskaug 氏記述のバージョンをクリックして選択後、revert ボタン(あるいは、右クリック→ revert to selected version )でいつでもこの変更を取り消すことができます。
ついでに、最後に使うソースコード閲覧用の HTML ファイル生成時にロールメソッドを列挙できない同根のバグも退治しておきましょう。同じようにワークスペースなどで「runtimerole」などとタイプした後、alt + q で「runtimeRoleClassForRoleName:」と補完してから alt + m でメソッド定義を呼び出します。「roleName」の前に「'_' , 」を挿入し alt + s(もしくは、右クリック → accept )でコンパイルすれば終わりです。
無事コンパイルできたら、インストール直後と同じく次回以降の起動でも有効になるよう忘れずに、Squeakメニュー → Save Image しておきましょう。
ローンシンジケートの例を組む
こちらのローンシンジケートの例
を端折ったものを書いてみます。内容としてはエヴァンスのDDD本の8章と10章などに登場する投資銀行のシンジケートローンを管理する巨大アプリケーションのコアを開発したときの話を基にしたもので、複数の投資会社(銀行)が共同で出資して金を貸し出したり、その一部が返済された場合に、各社の負担額はどう変わるか?といった処理を記述する課題です。
DCIアプリケーション「BBLoanSyndicate」の作成
Apps → BB1: IDE → other... → BBLoanSyndicate と入力 → Accept すると空の babyIDE が開きます。
データモデルの定義
まず、ウインドウタイトルバー右側にある青い▼ボタンをクリック して new projection... を選択します。すると、Error: BUGGY OPERATION というノーティファイアが現れますが、ひるまず Proceed すると入力を促されるのでそこで Data とタイプしてから Accept します。再び、Error: buggy method. と注意されますが、無視して Proceed すると Data タブとクラスブラウザライクな画面が現れます。
下のペインの内容を以下のように書き換えるか、ここからコピペしてコンパイル(alt + s もしくは右クリック → accept )します。
Object subclass: #BBFacility instanceVariableNames: 'limit loan shares' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Data'
同様に BBFacility の定義を次のように書き換えて、BBLoan を定義します。
Object subclass: #BBLoan instanceVariableNames: 'shares' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Data'
続いて、それぞれのクラスに暗黙に呼ばれる初期化メソッド(initialize)を定義します。
- クラス名リスト枠(上段右端)で BBFacility クラスをクリックして選択
- メソッドカテゴリーリスト枠(上段右から三番目のペイン)で no messages をクリック
- 次のメソッドを入力し、alt + s でコンパイル
- メソッドカテゴリーリスト枠が no messages から as yet unclassified に変わるので、右クリック → rename category... → initialization と入力 → Accept で変更
initialize
shares := Dictionary new
同様の操作で BBLoan にも同じ内容の initializa メソッドを定義します。
後ほどインスタンス変数へのアクセッサーメソッドを自動生成する作業がありますが、とりあえずデータモデルの定義はこれで終わりです。(簡単のため元コードの Share と SharePie は定義しないことにします。)
コンテクストの’定義
データモデルのときと同様にコンテクストを定義するためのContextタブを追加します。
ウインドウタイトルバー右側の青い▼ボタンをクリック して new projection... を選択し、ノーティファイアが現れてもひるまず Proceed しつつ Context と入力 → Accept します。
現れたContextタブ(ボタン)をクリックして選択したあと、データモデルのときと同様に下のペインにコンテクストBBLoanSyndicateCtxを次のように定義します。
BB1Context subclass: #BBLoanSyndicateCtx instanceVariableNames: 'facility' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Context'
データモデルおよびコンテクストへのアクセッサーの一括定義
以下ではデータモデルおよびコンテクストのインスタンス変数に対するアクセッサーメソッドが定義済みであることを前提にしているので、このタイミングでこれらを自動生成しておきます。
適宜、タブを切り替えて、Dataタブの BBFacility、BBLoan、Contextタブの BBLoanSyndicate をクリックして選択してそのままマウスポインタを枠から出ないようにして(枠の中で)右クリック → create inst var accessors を選択します。
アクセッサーメソッドを自動生成したクラスのメソッドカテゴリーリスト枠には accessing プロトコルが追加され、そこにインスタンス変数と同名のゲッターと、それにコロンを付したセッターが定義されていることを確認します。
ロールの定義
さて、次にロールの定義なのですが、Contextタブに先述の SWAP TO INTERACTION ボタンが見当たりません。
これは、タイトルバーの青▼ボタン → set application... → BBLoanSyndicate を選択することで babyIDE をリフレッシュすればよいようです。Data、Contextタブの表記も簡潔になり、改めて Contextタブをクリックして切り替えることで SWAP TO INTERACTION ボタンもちゃんと現れるようになります。
では改めて、SWAP TO INTERACTION ボタンをクリックしてロールの定義を始めましょう。
コンテクストに参加するロールとそのインタラクション(メソッド)を定義します。
まずロールとそれら相互の依存関係を定義します。ここまでの Smalltalk の伝統的なクラスブラウザの作法でのクラス定義と違い、楕円で示されたロールを作り、依存関係はそれらを矢印でつなぐことでダイアグラムとして図的に記述します。
- 左上枠のBBLoanSyndicateCtx(コンテクスト)をクリックして選択
- 右上の枠で右クリックして add role → Lender と入力 → Accept (alt + s)
- Lender ロールを示す楕円を枠内の適当な場所をクリックして配置
- 同様に、AmountPie、PercentagePie を作成して配置
- Lender を右クリックして add link → AmountPie をクリック
- AmountPie を右クリックして add link → PercentagePie をクリック
インタラクション(ロールメソッド)の定義
そして DCI の I 、各ロールにインタラクション(ロールのメソッド)を定義します。
- ロールをクリックして選択 → 下のコード枠に次に示したメソッドを入力するかコピペ → alt + s で accept
定義するメソッドは4つ(Lender の draw: と pay: 、AmountPie の increase: と decrease: )ですが、冒頭の「ロール名 >> 」より後がメソッドのコード本体なので、そこだけコピペ(あるいはタイプして入力)して「ロール名 >> 」の部分は含めないでください。Smalltalk 界隈では複数の異なるクラスに属するメソッドのコードを列挙して示す必要がある際に、そのメソッドがどのクラスのものかが区別できるようにそれぞれの頭に「クラス名 >> 」と書く慣習があります。ここではロールをクラスに見立てて、同様の記法を用いました。
たとえば Lender の draw: メソッドであれば、Lender >> の直後の draw: amount から3行目の limit - amount の行までをタイプして入力するか、選択してコピペしてください。その後の accept(alt + s) もお忘れなく。
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 + s)。
Lender ^facility
- 同様に AmountPie、PercentagePie も定義を次のように書き換えて accept (alt + s)。
AmountPie ^facility loan
PercentagePie ^facility
各ロールへのデータマッピングメソッドの定義が終わったら Message Category Browser (BBLoanSyndicate) ウインドウは綴じてしまって構いません。
なお BabyIDE の場合、ロールの割り振りに相互依存性があったりしてその順序が重要な場合には、コンテクストの remap をオーバーライドしてデータマッピングメソッドのコール順を明示的にしておく必要があります。しかし、この例ではその必要はなさそうです。
各ロールにマウスをホバーすると、吹き出しにデータマッピングメソッドが表示されるので、以降は簡単に確認できます。
コンテクストへの各種メソッドの定義
BBLoanSyndicateCtx に初期化メソッド(#buildFacility、#joinFacility:percentage:)、および、トリガーメソッド(#draw: #pay:)を定義します。
- SWAP TO CONTEXT ボタンをクリックして画面切り替え
- 第三枠(カテゴリーリスト枠)で右クリック → new category... → initialization と入力してから Accept (alt + s)。
- 追加された initialization をクリックして選択。
- 次のそれぞれのメソッド定義を下段コード枠にペーストして 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
- 第三枠(カテゴリーリスト枠)で右クリック → new category... → triggers と入力してから Accept (alt + s)。
- triggers をクリックして選択。
- 次のそれぞれのメソッド定義を下段コード枠にペーストして accept (alt + s)。
draw: amount self triggerInteractionFrom: #Lender with: #draw: andArgs: {amount}
pay: amount self triggerInteractionFrom: #Lender with: #pay: andArgs: {amount}
動作確認コード
以上で以上で定義は完了です。実際に動作を確認してみましょう。Data や Context と同様の方法で新しく Testing というタブを作り、そこに動作確認用の適当なクラスを定義して、クラスメソッドとして example メソッドを追加しましょう。
- ウインドウタイトルバー右側にある青い▼ボタンをクリック して new projection... を選択、Testing と入力して Accept (alt + s)。(例によって、途中で出てくるノーティファイアによる警告は無視して Proceed で続行。また追加後、Data、Context を含む冗長な表記のタブがずらずら現れるので、set application... から BBLoanDyndicate を選択してリフレッシュ。)
- データモデルやコンテクストのときの要領で BBLoanSyndicateTesting クラスを Object のサブクラスとして定義。
Object subclass: #BBLoanSyndicateTesting instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'BBLoanSyndicate-Testing'
- 次の example メソッドをクラスメソッドとして定義するため、クラスリスト枠(第二枠)下にある class ボタンをクリックして選択し、クラスメソッドサイドに切り替え。
- メソッドカテゴリペイン(第三枠)から no messages をクリックして選択してから、次のコードを入力するかコピペして accept(alt + s)。
example "BBLoanSyndicateTesting example" | syndicate | syndicate := BBLoanSyndicateCtx 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 + d) すると、トランスクリプトが前面に現れ、結果が出力されます。
BBLoanSyndicate全ソースコード
青▼ → printHtml for this App で生成(一連のファイルは ./Squeak-4.5-All-in-One.app/Contents/Resources/BBLoanSyndicate-listing に出力)される、ソースコード閲覧用の HTML ファイルを下の場所に置いておきます。