言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

エディタのデザイン

金曜日にプレゼンがあって、久しぶりに徹夜した物だから週末はめちゃくちゃしんどかった。ので、今日はほとんど一日寝ていた。まだしんどい。が、せっかくなので内部でプレゼンしたスライドをアップする。

dataflow

エディタのような普通のアプリをどうやったら綺麗に記述出来るだろうかという話です。まー例えば入力が元のテキストとキーボードイベントで、出力がテキスト画像と変更後のテキストであるような関数をエディタだと考える事が出来ます。

simpleEditor

これを素直にプログラムで書くと、こんな感じになります。上からイベントがやってきて、テキストの内部表現(TextBuffer)を変更します。それを画面に表示するにはレイアウト用のデータ構造(TextView)を作成して、さらにビットマップに変換してデバイスに送ります。確かに単純ですが、これを副作用抜きで毎回オブジェクトを作るとなると遅すぎて動きません。数百文字で挫折します。

realEditor

という事で、普通は差分に着目して、イベントがやってきた時に前回からの変更点だけを書き換えるという風にします。

編集前の時刻 t0 の時点のテキストを TextBuffer0 だとして、編集後 t1 のテキストを TextBuffer1、その変更点を Δbuffer という風に書きます。Δバッファはまあ、編集コマンドみたいな意味合いです。同じように TextView の変更点を Δview、表示領域 (Display) の変更点を Δdisplay と書きます。表示領域の変更はいわゆる damage rectangle と同じ事です。すると、Δview は Δbuffer の関数、Δdisplay は Δview の関数と考える事が出来ます。副作用があるといっても、副作用の差分に着目するとちゃんと関数になっているのです。

ここで、TextBuffer0 -> TextView0 -> Display0 の関係から、Δbuffer -> Δview -> Δdisplay の関係作る事を最適化と呼ぶことが出来ます。

assoc

編集コマンド Δbuffer には他にもいろいろ面白い性質があります。二つの編集コマンドを並べて実行する事を Δbuffer0 ; Δbuffer1 のように書く事にします。するとコマンドの列には結合法則があります(例: マクロ実行)

inverse

次に、何もしない編集コマンド Δid (ちゃんと書くと Δbufferid) を考えます。そして、ある編集コマンドの逆の働きをするコマンドを Δbuffer-1 のように書きます。すると、Δid = Δbuffer; Δbuffer-1 という性質があることがわかります。これが UNDO の表現です。

大事な事を言い忘れましたが、同じ関係が TextBuffer や Display でも成り立つ事が味噌です。

relation

理屈はここまでしにして、どうやってオブジェクト指向言語でこれを表現するかという話です。オブジェクト間の基本的な関係を V (view) knows M (model) だとすると、V と M の間には二つのデータフローがあります。一つが V から M への呼び出し (call)、もう一つが M から V への返り値 (return value) です。重要なのは、M は V が誰なのか知らない点、Observer pattern (ここでは signal と呼びます) も返り値とみなす事が出来るという点です。

signal

関数呼び出しと、シグナルを比較してみます。View、 Model 間の関係に着目すると、違いはタイミングで、同じ構造だと言う事が出来ます。つまり、オブザーバーパターン(Signal) は関数の返り値を最適化した物だといえます。

detail

最終的に出来たオブジェクト構造です。引数によるデータの流れ、点線が返り値または Signal によるデータの流れです。データフローの観点からはTextController は TextView とくっつけても構いませんが、分けると何かと便利です。

と、こんな事をやった。まだきっちりしたアイデアでは無くて説明が上手く出来なかったので、一瞬ポカーンとされたかな。しまった!と思ったけど、みんな後でなかなか面白かったよ!と言ってくれたので良かった。それにしても体がだるい。