意味不明な日記かとは思いますが、閃いた記念に書きます。あとでちゃんと説明用の文章を書く予定。
概要: フレームワークの中に依存関係がある時、監視元は依存ごとにプロセスを一つ作り依存が有効な期間中存続させる。監視プロセスは実際に監視先に更新が起こるまで処理をブロックする。更新が発生すると、監視先はその値の監視プロセスを全てブロック解除する。通常はブロックの後ろに新しい値を返すコードが書かれているので、全ての監視プロセスは新しい値を得る事が出来る。
問題: eToys モデルで全ての GUI を設計するにはどうしたら良いか?
定義: eToys モデルとは、無限ループを続ける沢山の小さなプロセスが通信する世界の事とします。
目的と背景: 理解しやすい GUI フレームワークを作る事が目的です。戦略のポイントは二点で、更新モデルと分割モデルです。更新モデルとは、MVC のようなオブザーバーパターンを使うか、一部の Morphic のようなタイマーを使ったポーリングを使うか(eToys モデルと呼びます)という事で、分割モデルとは、以前 GUI Architectures http://d.hatena.ne.jp/propella/20071230/p1 で書いたオブジェクトの空間的な分割方式の事だけど、今回更新モデルに絞って書きます。
例えば温度計を作るとします。温度の値はセンサーかウェブかどこからか拾ってきて、それを画面に針で表示するとします。この場合、針は温度の値に依存しています。eToys モデルでは、針に仕込んだプログラムを例えば一秒ごとに起動して、その時点での温度を元に針を動かします。温度が変わっていなくても一秒ごとに温度を監視しなくてはいけません。MVC の場合、温度が変化した際に通知が発生し、初めて針が動く事になります。この場合、MVC の方が効率の良いプログラムが書けます。
一方で、時計を作るとします。ミリ秒ごとのシステムタイマをモデルとした場合、eToys では予め指定したタイマより短い頻度で時刻を監視する事はありませんが、MVC ではミリ秒ごとに時刻を監視する羽目になり効率は逆になります。
この良い所取りをするのが以下の方法(js 風擬似コードスケッチ)。
// モデルのメインプロセス function Timer.prototype.mainProcess() { while(true) { // 例えば、システムクロックが変わっていたらメンバに代入して if (this.time != systemClock()) { this.time = systemClock() this.unblock(this.getTime) // getTime を監視しているプロセスを再開。unblock の引数は関数への参照。 // 全ての監視プロセスの実行が終わるか、さらにどこかでブロックされるのを待って処理を続ける。 } } } // モデルのアクセッサ function Timer.protoype.getTime() { block() // time を監視しているプロセスは一旦ブロックされる。 return this.time // this.unblock によってブロックが解かれ更新後の time が返る。 } // ビュー(監視する)側のプロセス function Clock.prototype.minuteProcess() { while(true) { this.minuteHand = timer.getTime() // getTime によって一旦ブロックされる。 this.unblock(this.getMinuteHand) // もしも minuteHand を監視しているプロセスがあればブロックを解除して値を返す。 sleep(1000) // そんなに頻繁に見なくていいので寝る } } // おまけ、もしもさらにビューを監視するオブジェクトがあれば再帰的に依存ツリーを辿る。 function Clock.prototype.getMinutesHand() { block() // minuteHand を監視しているプロセスがあれば休憩 return this.minuteHand }
プロセスと書くとおどろおどろしいが、平行に動く必要は無いので実装は単一スレッドの普通のオブザーバーパターンで良い。別の言い方をすると、オブザーバーパターンをファイルハンドルのブロック API のように扱うという事。発想のヒントは、オブザーバーパターンを遅延された関数の戻り値と考える事が出来るという発見からでした。