こんにちは、タナカです。
ここ数年で、リアクティブプログラミングというキーワードをよく目にするようになりました。
僕も記事を読んだり、FRPのライブラリをいろいろ試したりはしているのですが、リアクティブプログラミング自体への理解がイマイチな状態でした。
本日は勉強会で発表するといういい機会がありましたので、今回リアクティブプログラミングについてまとめてみました。(内容等に間違えがあればご指摘いただけると助かります・・・。)
リアクティブプログラミングについて
wikipediaでは以下のように説明されています。
- 値の変更を伝播させるデータフロー指向のプログラミングパラダイム
- 値の関係性を記述してプログラミングする
これらについて実際の例を使って説明していきたいと思います。
リアクティブプログラミングの例
リアクティブプログラミングを説明するのにExcelの例がよく利用されます。
こちらの例は、セルC1に式を定義し、セルA1、セルB1に入力された値が自動で計算されるというものです。
セルA1、セルB1の値に変更があってもExcelが自動的にセルC1を再評価してくれます。
「 値の関係性を記述してプログラミングする」これこそがリアクティブプログラミングです。
(リアクティブプログラミングを実現するExcelのことをリアクティブアプリケーションと呼ぶようです。)
このExcelシートを従来のプログラミングパラダイムで記述してみます。
var updateC1 = function() { cellC1.setValue(cellA1.value + cellB1.value); } cellA1.addEventListener(‘change‘, updateC1); cellB1.addEventListener(‘change‘, updateC1);
jQuery等を利用すればもう少し簡潔に書けますが、だいたいこんなコードになると思います。
この時点ではExcelの定義に比べて少し短くなった程度です。
次に計算式と変数を一つづつ加えてみます。
先ほどの例と比べて、セルD1の値とセルE1に計算式が追加されただけでです。
セルA1、セルB1、セルD1のどの値が変更しても計算式のセルC1、セルE1は自動で結果を表示します。
こちらも従来のプログラミングパラダイムで記述してみます。
var updateC1 = function() { cellC1.setValue(cellA1.value + cellB1.value); } var updateE1 = function() { cellE1.setValue(cellC1.value * cellD1.value); } cellA1.addEventListener(‘change‘, updateC1); cellB1.addEventListener(‘change‘, updateC1); cellC1.addEventListener(‘change‘, updateE1); cellD1.addEventListener(‘change‘, updateE1);
先ほどのコードに比べてコード量が結構増えました。
セルE1の内容を最新の状態に保つために、セルC1とセルD1の内容を監視しておく必要があります。
この例ではExcelの方がだいぶ簡潔に記述できると思います。
リアクティブプログラミングで記述すると、リスナーを登録するというコードが不要になります。
従来の方法だと、どの値に変更があったときにどの値を更新するという関係をコード中に記述することになります。
リアクティブプログラミングのメリット
まとめるとリアクティブプログラミングのメリットは以下になります。
- 従来の方法より記述がシンプルになることが多い(ならないこともある)
- Observerパターンを意識する必要がなくなる
加えて、分散システムで使用すると従来の方法よりもリソースの利用効率がよくなるらしいですが、こちらについては今回触れません(まだ理解が追いつかない・・・)
リアクティブプログラミングで扱うとよいもの
以下のようなものを扱うとリアクティブプログラミングの本領が発揮されます。
- GUIでの入出力
- 時間とともに状態がかわるもの
- 非同期の通信処理など
GUIについては先程Excelで説明したとおりですが、「時間とともに状態がかわるもの」としてはアニメーション処理などが該当します。
「非同期の通信処理」はJavaScriptで非同期I/Oの処理を記述するときのコールバックをイメージしてもらうといいかと思います。
Promise等を使わずに記述するとコールバックのネスト(いわゆるコールバック地獄)になりがちですが、たとえばRxJSというリアクティブプログラミングのライブラリを使用するともっと綺麗な記述ができるようになります。(場合によってはならない・・・。)
RxJsについては以下のサイトが詳しいです。
リアクティブプログラミングで重要な技術
依存グラフとキャッシュ
依存グラフとは値同士の関係性を表すものです。
こちらの図は、先ほどの例(Excelの2つめの例)を依存グラフで表したものになります。
この依存グラフから、Dに対して更新があった場合にCより上については影響が出ないことがわかります。
これを先ほどの例で説明したいと思います。 まずはAに1、Bに2、Dに3がセットされた状態です。
依存グラフは下から見ていきます。
Eの値が必要になったタイミングで対応する式「C*D」の計算を行ないます。
Cの値がわからないので、対応する式「A+B」の計算を行ない、Cの値として"3"がキャッシュされます。
Cの値が取得できたら「C*D」の計算を行ないE=9という値が算出されます。
このあと、Dの値が"3"から"4"に変わったとします。
再びEの値が必要になったタイミングで対応する式「C*D」の計算を行ないます。
今度はキャッシュを参照しC=3と既にわかっているので、「C*D」の計算を行ないE=12という値が算出されます。
依存グラフとキャッシュを使用すると値が必要なときに必要な計算だけを行なうことができます。
差分適用
リアクティブプログラミングで値を伝播させる際は、差分適用という仕組みを利用します。 以下の図は伝播元のDに変更があった場合に、伝播先には変更のあったDのみを伝えるようすを表しています。 変更のあった値に絞って伝播させるので送信効率がよいです。
遅延評価
依存グラフでも少し触れましたが、リアクティブプログラミングでは値が必要になるまで式や関数の評価を行わないという特徴があります。 わかりやすくするために遅延評価を論理演算で表してみたいと思います。
Gの値を求めるのにCとFを評価する必要があります。
まずはCを評価しますが、仮にC=1だった場合、Fの評価を行わなくともG=1が算出されます。この場合、Fの評価は行われません。
遅延評価を行うと必要なときだけ値が計算されるので全体を計算するのに比べて、計算量を低減できます。
といっても、論理演算であれば従来のプログラミングパラダイムでも同様の計算量となります。
しかしながら遅延評価では、論理演算にかぎらず関数を扱った場合も計算量を低減できます。 C、F、Gが関数であった場合でも、まずGを評価して、Cを評価するので、Gの内容次第では評価を省略できます。
リアクティブプログラミングを利用するには
以下のライブラリを利用するとリアクティブプログラミングでプログラムを書くことができます。
リアクティブプログラミング関連のライブラリはたくさんありますが、メジャーなもののみピックアップしてみました。
FRPライブラリ
FRPとは関数型リアクティブプログラミングの略で、関数型言語の特徴であるmap、reduceやfilterを使用してプログラミングを行うものです。
Data Bindingライブラリ
Data Bindingライブラリは主にGUIまわりでの同期を自動化するためのものです。
- ReactiveCocoa (for iOS)
- React (for JavaScript)
リアクティブプログラミングは流行るか?
リアクティブプログラミングは最新技術に敏感な方であればここ数年でほんとによく聞くようになったと思いますが、知らない人もまだまだ多いと思います。
個人的な意見になりますが、リアクティブプログラミングはGCの技術に似ていると思います。
GCはメモリーの開放を自動化してくれましたが、リアクティブプログラミングは変化する値の同期化を自動化してくれます。
今現在、C言語などを除くとほとんどの言語でGCが利用されています。
そのうちリアクティブプログラミングで書くのも当たり前の時代が来るのではないかと思います。
まとめ
- リアクティブプログラミングはデータフロー指向の新しいプログラミングパラダイム
- リアクティブプログラミングを利用することでプログラムをシンプルにできる場合がある
- リアクティブプログラミングが流行る日も近い?