しばし見かけるReactive Extensions(ReactiveX, Rx)に関する説明の多くはファンクショナルだのリアクティブだのモナドといったキャッチーなフレーズを使っている。けれども、そういう方面に馴染みのない人を相手にして、そもそもの概念的に何を解決したかったものなのかという説明があんまりない気がした。ユースケースを伴った説明も局所解すぎて現実における使い道がわかりにくい、というか誰も彼もデータバインディングしか例に出さないのはどうなんだ。これはストリームの川?それは表現形態であって実態を表しているとは言い難いだろう。
放置していても誰も書かない気がするし、神秘的な霊験と共に語られても全く役に立たないし、自分の思考の整理と(幾らかは同僚への説明も兼ねて)書いてみることにする。
もしかすると.NET界隈あたりでは過去にやりつくしたネタの再生産かもしれないし、ラジオなどの非文章媒体で既にあるのかもしれないけれど、見つからなかったので、まあいいや。
プログラミングの手法としてイベント駆動でコールバックを呼び出すパターンが存在する、というのは周知の事実。これは「何かが完了したら、それが変化したことを任意の対象に対して通知する」のに役立つ。典型的なのはGUIプログラミング。ユーザーがクリックしたことがイベントとして通知され、それを受けて処理を行う。ネットワークリクエストの結果を受け取るコールバックも、レスポンスが帰ってきたという局所的なイベントを受けて動作する点で変わりはない。
プログラミング的にもイベントが飛んでくるという抽象化がなされている方がやりやすいことは、それなりにある。もしイベントがないと延々とポーリングをして状態の変更を確認し続けなければならない。面倒臭い。
ところが、この古典的なイベント処理には問題がある。イベントというのは本質的に非同期に突如としてやってくるものであり、それぞれがバラバラに飛んでくる(離散的に、と言い換えても良いかもしれない)ものであるのが殆どだからだ。ネットワークレスポンスの終端イベントは開始イベントの後にやってくる、という程度の順序はあるけれど、個々のイベント自体は、自信がどういう順序関係で発行されたのかを情報としては持ち得ていない。
イベントが全部バラバラに飛んでくる以上、「画面がリサイズされた後にクリックが起こった場合」というような状況を適切に処理したい場合、各イベントのユーザーであるプログラマは、自分でフラグを立てるなどをしてイベントの順序を管理するしかない。イベントAに対応するフラグA’を立てて、その状態でイベントBが来たらB’を立てて、A’とB’の両方がtrueであれば後続に処理をつないでいきetc。フラグが増え続け、破綻する日は近い。
イベントAとBが両方とも来たことを示すCというイベントを発行する、という手法もあるが、これはイベント粒度を細くすればするほどやることが増えていくし、各イベントが取り扱う変化の範囲で悩み始める。
このイベントが多すぎる・関係性を持ち始める問題は、わかりやすく例えれば、GUIがリッチになるにつれ問題となっていく。GUIの変更・ユーザーの操作それぞれが条件となり、次に起こすべき変更もさらに条件となっていく。しかも、これがモバイル端末の上ともなると、ネットワークの状態、バッテリーの具合などが出てきてカオスとなってくる。
こうした状況に対してLINQ to Eventsとも呼称されるReactiveXが提示した解決策は、イベントは離散的なデータの集合であり、それらから任意のイベントの関係性を取り出す方法があれば良いということ。度々例示してきたように、一見バラバラで独立している個々のイベントにも、実際に利用する際には関係性が存在している。その関係性をコードとして明示しクエリとして処理することで、コードを通して人間にも読める形式で、機械的に結果を取り出すことを可能にした。この結果、複数のイベントの関係性から別のイベントを生成することが容易になり、イベントに紐づく変化の範囲についてコードを書く側が悩む必要もなくなった(必要に応じてイベントを細かく割りやすくなった)。
これと同時にスケジューラという概念を導入することで、取り出したイベントを後続に伝えるタイミングや、イベントを発行する対象のスレッドなども一連の流れの中で制御できるようにした。
それと、イベントを文字列やsymbolのような固定値ではなくオブジェクトとし、構文ないしメソッドの形式でイベントの関係性を表現したことで、コード解析技術を通してイベントの関係性の解析・図示や入力補完といったことが可能になった。これにより、アプリケーション規模が巨大になっていってもIDEなどを通してコードを掌握しきることができるので、ある種のgoto jumpであったイベントハンドリングがコード上に静的な形式で落とし込まれることになった。
私のブログを読んでいる人はJavaScriptの読み書きをする人が多いと思うので、JavaScriptのPromiseとの関連で説明しておくと、Promiseは原則として1回しか自身の状態を変更できない = 1回限りのイベントにしか使えない(なので、実質的にはpull型で動き始める)。何回でも発生しうるイベント全般に対する適用ができないから、Promiseではイベントの関係性は表現し得ない。
さて、先述のような抽象化を実施するために、Rxの実装の裏側では多くの一時オブジェクトを生成するなどしているため、実行時のコストがかかるように思える。だが、ハードウェアの進歩とコンパイラ・VMの進化、及び抽象化に伴うプログラミング時の生産性の向上と合わせると、アプリケーションのレイヤーではコストとして無視するという選択が可能になった。コストが無視できない環境では従来通り頑張って書けばいいだけで、事態が悪化したわけではないし、言語そのものを乗り換える(JS -> Java/C# -> Cpp)ことでも案外解決できたりする。
また、イベントを発行することに伴うプログラミング時の負担が減った結果、状態の変更をイベントとして気軽に通知しやすくなった。これにより、ある意味では副作用を起こしやすくなった。副作用による問題の一つは、ある状態に依存している(ある状態であることを仮定している)後続の処理群が、前提条件の変更を適切にハンドリングできない点にある。故に、気軽に状態が変更された事実をイベントとして伝え、再度計算し直すことで、全体としての整合性を保てるようになる(だからと言って、気軽に手当たり次第に副作用を起こしていいかというとそれは別)(再計算によるコストも、前述の通り実行環境の進化で無視できるという前提に立っているので、無視できない変更が起こるケースは再計算せずに済ませるようにした方がいい)。
ReactiveX.ioのイントロに「俺たちはFunctional Reactive Programmingじゃない」って書いてあるのは、各自読んでもらうとして、だいたいこんな感じ?