本記事では、2015/2/21に行われた「Frontrend Conference」のJSトラックセッション「Reactive Programming in JavaScript」から抜粋した内容を紹介します。
「リアクティブプログラミング」(以下、RP)という言葉を、みなさんはご存じでしょうか。RPは、オブジェクト指向プログラミングや関数型プログラミングと同様に、プログラミングパラダイムのひとつです。このRPというパラダイムを、近年のWebフロントエンドの風潮になぞらえて見ていきます。
リアクティブプログラミングという考え方
RPは「イベントや変化する値の関係性」に注目し、これを簡潔に記述することで相互作用を上手に扱おうとするプログラミングパラダイムです。
これはオブジェクト指向プログラミングが「オブジェクトと相互のメッセージング」に注目したり、関数型プログラミングが「関数の作用」に注目したりするように、プログラミングを捉えるときの着眼点、切り口のひとつであるとも言えます。
GUIにとってReactiveは身近な概念
GUIプログラミングにおいて、データ(一般的には、Model)に変化があったときや何らかの非同期処理が終わったときなど、表示(一般的にはView)の更新につながる処理が頻繁に実行されます。
RPでは「イベントや変化する値の関係性 = データフロー」に注目した上で、特に「データフローの宣言によって、片方の変化を他方に自動で伝播させる」ことが重要です。これによって、しばしば複雑になりがちなデータの相互作用を容易に管理できるようにします。
GUIで発生する同期/非同期を問わずに発生する何らかのイベントと、それに伴う画面の更新は「片方の変化を他方に自動で伝播させる」仕組み作りが有効に作用する分野です。
Reactiveなアプローチはフロントエンドでも試みられてきた
Webフロントエンドでは、これまでどのようにReactiveなアプローチが試みられてきたのでしょうか?RPの主題とも言える「データフローの宣言によって、片方の変化を他方に自動で伝播させる」という振る舞いの成り立ちは、作ろうとしているプロダクトの種類や開発言語、使用するライブラリによって異なります。
たとえばMVVM(Model View ViewModel)に代表されるようなデータバインディングの仕組みは、データ(Model)と表示(View)を宣言的に結びつけるReactiveな仕組みと言えます。
今年から特に話題にのぼることが多くなったReactについても、Fluxというアーキテクチャモデルが加わることで、データの変更を表示に伝播させるReactiveなデータフローを実現しようとしています。
Functional Reactive Programming
Functional Reactive Programming(以下FRP)とは、RPのモデルに関数型プログラミング(Functional Programming)の要素を加えたものです。
本セッションではデータバインディングや Fluxに続く(正確にはそれらよりもずっと前からあるものなのですが…)Reactiveなアプローチとして、FRPにという考え方に基づいた Reactive Extensionsと呼ばれるライブラリを紹介していきます。
Reactive Extensions と非同期データストリーム
FRPを基にしたReactive Extensions というライブラリでは、すべての値を「時間軸に沿って値が流れるリスト = 非同期データストリーム」というモデルに落とし込みます。これだけだと、少し分かりづらい概念かもしれません。
参考: Functional Reactive Programming with RxJS
少々強引ですが、今風に喩えて言えば、イベントが起こる度に何度も値を流してくるPromiseと捉えることもできます。それをリストとして捉えることで、map
やfilter
といった高階関数を活かした、関数型のイディオムを適用できるようになります。
非同期データストリームのモデルが優れているところは、Promiseのような非同期であっても、散発的に発生する click のような非同期イベント、通常の値であっても、すべて同じモデルに落とし込んでRPを実践できるように抽象化されているところです。
RxJS を利用してみる
ここでは Reactive Extensions の JavaScript実装であるRxJSを取り上げて、一つの例を紹介します。次に示すリンクは、+(プラス)または-(マイナス)をクリックすると、中央の数字が更新されるサンプルです。この中にRxJSで使われる基本的なメソッドが詰まっています。
上記サンプルのJavaScript部分を抜粋してコメントをつけると次のようになります。サンプルの動作自体は簡単なので、らくに読めると思います。中でもストリームやオブザーバを宣言している部分と、それらをどのようにつなぎ合わせているかに注目するとよいでしょう。
document.addEventListener('DOMContentLoaded', function() {// 要素のセットアップ var plusEl = document.getElementById('plus'); var minusEl = document.getElementById('minus'); var counterEl = document.getElementById('counter');
// <span id="plus">+</span> を click すると 1 が流れるストリーム var plus = Rx.Observable.fromEvent(plusEl, 'click').map(1); // <span id="minus">-</span> を click すると -1 が流れるストリーム var minus = Rx.Observable.fromEvent(minusEl, 'click').map(-1);
// plusとminusをひとつにマージする var both = plus.merge(minus);
// scanは第一引数に初期値、第二引数にアキュムレータを受け取る // curtValueは現在値を示し、bothから1/-1が流れてくるたびに加算する var curtValue = both.scan(0, function(acc, v) { return acc + v; });
// 流れてきた値を処理するオブザーバを定義する var setHtml = Rx.Observer.create(function(v) { counterEl.innerHTML = v; });
// curtValue (現在値) に更新があると、オブザーバが動作する var subscription = curtValue.subscribe(setHtml); });
非同期データストリームにほとんどについて、Rx.Observable
のインターフェースに集約されており、全て取り上げると莫大な数(150超)のメソッドが実装されています。(APIドキュメント)
いきなり全てのメソッドを把握しようとすると、あまりにも大変ですが、まずは今回のサンプルで登場したような限られたメソッドを把握するだけでも、簡単な動きを作ることはできるでしょう。
- Rx.Observable.fromEvent(element, eventType)
- Rx.Observable#merge(otherObservable)
- Rx.Observable#scan(seedValue, accumlator)
- Rx.Observable#subscribe(observer)
本編セッションでは、より多くのサンプルを紹介しているので動画やスライド資料も参考にしてみてください。
RxJS に影響を受けたその他のライブラリ
Webフロントエンドで利用できるFRPライブラリは、RxJSだけではありません。Reactive Extensions、あるいは RxJSそのものに影響を受けて作られたライブラリが他にあります。
Bacon.jsはRx.Observable
相当の概念を、EventStreamというProperty に整理しています。これはReactive ExtensionのObservableにおけるHot/Coldという、やや複雑な概念をシンプルに整理し直したものです。
Kefir.js はBacon.jsと同じ概念を踏襲しつつ、コード実装を最適化して、より優れたパフォーマンスを得ることを目的としたライブラリです。
Reactive Programmingを学ぶ意義
FRPの考え方を忠実に再現しようとすること自体は、現在のフロントエンドにおけるJavaScriptの設計パターンと必ずしもマッチするものではないかもしれません。
しかし、オブジェクト指向言語に関数型のエッセンスが徐々に取り入れられているのと同じように、RPも少量のエッセンスを取り入れることには十分な価値があることでしょう。
本記事では限られた範囲での紹介でしたが、セッションの動画も公開されていますので、もっと知りたい方はそちらもご覧いただけると幸いです。2015年に入ってからReactive ExtensionsまたはRxJS関係の情報も増えてきていますので、今後より一層注目されるようになるかもしれません。