karino2


南よ! 海の見える方!


Previous Entry Add to Memories Share
AvalonからMVVM、そしてRxへ(その2): GUIプログラミングの哲学の歴史
karino2
何故か書きかけで放り投げた前編が妙に反響あったので、一応続きも書いておく。

UIのプログラムというのを、準静的に書く為だけに存在するViewModelという物を導入する事にして、現実の要求と準静的なUIのギャップをだいたい埋める事に成功した人類だが、2つほど問題が残った。

1. UIからViewModelへの通知の粒度のミスマッチ
2. GUIアプリでは非UIの機能を非同期で実装しなくてはいけないが、そことViewModelのマッピングでかつての動的なGUIと似た問題が発生してしまう

まず1について。
MVVMにおいては、直接イベントはハンドリングせず、基本的なUIの変化はViewModelのフィールドの変化にマッピングする(かICommandにマップする)。
例えばテキストボックスに値を入れると、対応するViewModelのstring型のメンバ変数(のsetter)に値が入る。

この対応がいつ起こるのか?というと、通常はUI側からは、テキストフィールドからフォーカスが外れた時となる。
このデフォルトの動作でだいたいはOKなのだが、しかし、たまにもっと細かく受け取りたい場合もある。文字数が140文字まであと何文字あるかを表示したり、とか、入力途中にサジェストを出したり、といううな場合。つまり、もっと細かく動的に振る舞ってくれるUIを作りたい場合だ。

そういう場合は一文字変化する都度setterを呼んでもらうようにする事が出来る。出来るんだが、、、
文字数を数える場合はそれで構わないのだが、サジェストの場合はそれだと少し細かすぎる。
本当は少しアイドルだったら受け取りたい。
それを一文字ずつの変化を受け取ってコーディングするのでは、動的なUIのコードと大差ないコードに戻ってしまう。フィールドに値がマップされてる、というよりは、onkeydownを受け取ってるようなコードになってしまう。

つまり、イベントの粒度が、フォーカスアウトのような大きな単位か一文字変更される都度のようなとても小さい単位かのどちらかしかなく、何らかの意味がある「間の状態」を作るにはイベントハンドラを手で書かなくてはならない。
そんなに良くあるシチュエーションでは無いので、その時は諦めてもいいのだけど、だいたいはGUIのコードが無い状態に出来てるのに、たまにちょっと書かなくてはいけないのはなんか悔しい(そして良くバグる)

次に2について。
これは本質的にはGUIの問題では無いのだが、ほぼ必ず発生するという点ではこちらの方が重要だ。
ようするにアプリのいうのは、テキストフィールドにURLとか入力してボタンをクリックしたら、HTTPRequestとか発行してデータが帰ってきたら表示したりする訳だ。
そのフローは動的で、昔のGUIプログラムに似ている。だからバグりやすい。
ほとんどが準静的になってGUI回りのバグが消滅すると、GUIプログラムを書く時にバグるのはVMと非同期コールの間に集中するようになる。
GUIではINotifyPropetyChangedとかDataBindとかいろいろな仕組みで動的な部分を制約していけているのに、非同期コールの回りは何の支援も無くて、突然厳しい大自然の荒野でコーディングしないといけない。割と似たような問題なのに、、、
感覚的にはVMとビジネスロジックの間にもXAMLみたいなレイヤーがあればM VM VM Vみたいな感じに出来そうだが、そうやってひたすら移譲を繰り返すのもなんか違う気がする。

どちらの問題もGUIプログラミングとしては諦めても良かったと思うのだが、Erik Meijerは諦めなかった。そこで出てくるのがRx。
MVVMに比べるとまだ知られていない技術と思うので、哲学的な話を少しこれまでより詳細に行う。

Rxとはなんぞや?
Rxとは新しいモジュール化の切り口である。
Rxではプログラムという物を、何かComputingが連続して行われる物、と解釈した。そしてその途中途中でチェックポイントのような物があり、そこではそれまでのComputingの結果の値が見られる。
つまりプログラムを
「Computing1+結果1」ー>「Computing2+結果2」ー>「Computing3+結果3」ー>、、、
という連なりだと解釈する事にした。

そしてこのかぎかっこの「Computing+結果」をモジュールの単位とするのがRxだ。

これではなんだか良く分からないので、もう少しプログラマ目線で考えよう。
これまでのモジュール化の方法というのは、

1. APIを決める
2. APIを実装する
3. クライアントはAPIを用いるコードを書く

という手順で行われた。
1と2がモジュールを実装する側で3が使う側となる。
Rxではこのモジュール化の方法が少し違っていて、

1. Computingの結果をチェックするチェックポイントの場所と、その時にチェックする値を決める
2. Computingとチェックポイントの単位で処理を書く
3. チェックポイントのタイミングと値をクエリ式を用いて自分の望むタイミングにカスタマイズする
4. イベントに反応するコードを書く

1と2をモジュールを書く側が行い、3と4をクライアントが行う。
カスタマイズという段階を挟んだのが委譲をどんどん続けてしまわない為にErikの出した答え。

コンピューティングと結果の列はLazyなリストとして表現される。nextに進む都度計算が進む、継続をオブジェクト化した物である。
これまではコードの中に暗黙で入っていたComputingや継続を実体化して名前をつけ、関数の引数などに渡して持ち回れるるようにした。これがRxの肝。

さて、そんな話は関数型ヲタクしか喜ばないだろう。そこで我々は、非同期なコードとVMのマッピングや、粒度のミスマッチとの関係を見ていく。

まず、Computingとチェックポイントのタイミングをカスタマイズする仕組みは、GUIのイベントの粒度のカスタマイズにそのまま使う事が出来る。
これはGUIが提供するイベントのタイミングを、制限された形でカスタマイズして、VMの望む形に、宣言的に変換出来る事を意味する。
この制限はカスタマイズが準静的な範囲になることを型チェックで強制する。細かい話をすればこれまで実体化してなかったComputingの継続という部分に型を与えて組み合わせを型チェックで制約し、モナド則を満たすような結合のみを許したのでこの上での合成はある種の正しさが保障される、という事だが、、、まぁ現実的にはマクロの代わりにテンプレートでコード書く事にしたようなもんで、使う側からすれば、型チェックで準静的な組み合わせ以外はコンパイルエラーになる、というだけに過ぎない。

これでイベントの粒度のカスタマイズという物を準静的に行う方法を(Rxから流用してるだけだが)手に入れた。
本質的に同じような問題に対して見た目の全然違うXAMLとクエリ式という解決策が混ざったのは歴史的経緯であって美しくはないが、美しくなくても解決は解決。ごった煮がC#スタイルである。

次に非同期とVMのマッピングはどうなるか?
Rxのやり方でHTTPRequestを使ったサービス呼び出しなどをモジュール化すると、イベントの列が従来のAPIに相当する口となる。
そしてそれのタイミングと値をクエリ式という名のDSLでカスタマイズしてVMの望むイベントとする事で、UIと同様にプロパティやコマンド(やメソッド呼び出し)とマップする事が出来る。
クライアント側であるVMとしてはカスタマイズとマッピングはクエリ式というDSLでやるので、GUI側のイベントカスタマイズ同様、準静的なマッピングが行えるようになった。

VMのマッピングはGUI側ではXAMLでやり、非同期呼び出し側とはクエリ式でやる
MV VMの名前との対応関係を見ておくと、

M

(クエリ式)

VM

(XAML)

V

こういう感じ。
LINQとXAMLでは表面上は全然違うが、本質的には大差無い。宣言的なDSLで準静的にマッピングを記述する、という事だ。
この図を見るとUIもLINQクエリ式でやったらいいじゃないか?という気もしてくるし、実際やってもいい気もするが、、、
GUIはデザイナが使うツールがサポートする都合で言語外DSLの方が都合が良く、イベントのカスタマイズはプログラマが柔軟にやりたいので言語内DSLの方が都合が良かった、という事だろう(昨今のVSの頑張りを見ると言語内DSLでもデザイナ用ツールは作れそうだが、、、)

歴史的経緯なのか理由があるのかは知らないが、とにかくこうなった。
VMはどちらとマップするにも宣言的な準静的なマッピングに制限されたDSLを手に入れて、動的で手続き的なコードを書く必要は無くなった。
GUIプログラミングとは、

1. 準静的な組み合わせにGUIの要求を分解、翻訳する作業
2. XAMLをデザインするお絵描き
3. ビジネスロジックをイベントの列で公開するプログラミング

がほぼ全ての知的作業となった。
そこに付随してGUIのデザインからほぼ機械的に決定されるVMの決まりきったコードを心を無にして手作業で書く作業と、イベントのマッピングをちょろっとBinding式とクエリ式で書く作業がおまけでつくが、どちらも基本的には一発で動いてバグらないし、開発時間に占める割合は微々たるものだ。

当初夢見ていた理想郷に比べるとずいぶんとかっこ悪い所も残ったが、Win32APIから続いたイベントドリブンなGUIのバグ、という問題は、これで駆逐出来た。実際もうそこのレイヤーではバグらない。
Rxのクエリ式は、たまに足りない語彙もあるのでそこはコードで足さないといけないが、どれも大した物じゃないし、それらがRxに含まれないのは思想的な瑕疵ではなく政治的な問題に過ぎない。
思想的にはこの分野の問題は片付いた、と私は思う。

You are viewing karino2