@ahomu
Reactive Programming in JavaScript
CAのひと
- What is Reactive Programming?
- Reactive in Frontend JavaScript
- FRP with Reactive Extensions
What is Reactive Programming?
- プログラミングパラダイムのひとつ
- イベントや値の関係性について着目する
手続きの記述→関係性の宣言
宣言的なデータフローをちゃんと示しているか 実装はコーディングやライブラリに依存する。
RP界隈はノイズが多い
広義や狭義が曖昧
- Actor Model(並列処理 分散システムの数学モデル)
Functional Reactive
もとはHaskellのライブラリ
関数型のパラダイムをとりこんだRP
Reactive Manifesto
- Reactive Streams
http://www.reactive-streams.org/
ここまではすごくふんわりしている。具体的なコードもないし。
JSでUIを操作する
APIからデータを受け取ってHTMLに反映する
だるいからライブラリを使う→Reactiveな仕組みが自然と求められる
これまでのJavaScriptのなかに見るReactive
- Observerパターンは初歩的な解決の一つ
- 宣言→より隠蔽して抽象化したもの
データバインディングは局所的なReactive(ViewとModelを宣言的に結びつける)
React with FluxはReactiveなデータフロー
- 一方向のアレを実現している
- コールバックヘルを避ける
UIでどうにかする
PromiseはClickのような離散的なイベントは扱えない
- Data BindingsはViewとModelを結びつける局所的なもの
非同期とか木にせずにすべて同じように扱ってなんでもReactiveに宣言したい(そのモデルは5年前からある)
→FRP
- 関数型プログラミング+リアクティブプログラミング
FRPのGUI的な代表格がReactive Extensions
RxAndroid
- ReactiveCocoa
- RxJS
Rxはどんなライブラリなのか
- すべての値や入力を非同期データストリームとしてみなす
- 入力にmapやfilterをつかう
- 非同期データストリームを中心につながりと処理を記述する
クライアントの処理は時間軸に沿って動く
Promise Array
非同期に複数の値がやってくる
Events as List
Promise
Array
ストリームをリストとみなすことで、関数型のイディオムを活かせるようになった。
Reactive-Extensions/RxJS · GitHub
Rx.Observable のインターフェースが中心になる
絶望の数(Observable Instance Methods)
変換処理がかかった次
(このあたりはHaskellとかScalaとかとおんなじような感じで、filterとかmapとかmergeとか)
promiseでいうチェーン
Maybe型のエラーハンドリングではなく、catchでエラーハンドリングしてる。
Rx.Observable.of(1, 2, 3) .map(function (x) { if (x === 2) throw new Error(); return x; }) .onErrorResumeNext(Rx.Observable.of(4, 5, 6)) .subscribe(function(value) { console.log(value); }); // => 1 // => 4 // => 5 // => 6
他にもretry(やり直し)もある。
フローコントロールの用途でAsync.js と読み替える例
メソッド多いけども。
各 FRP ライブラリの概観
- RxJS は ReactiveExtension 由来の正統派
- Bacon.js は RxJS を参考にした独自コンセプト
- Kefir.js は Bacon.js に似たAPIの軽量化版
- Meteor は Tracker.js を中心にサバクラで共有できる Reactive
- Elm は Haskell っぽいのから HTML/CSS/JS を生成する
hot observables と cold observables
See the Pen Draw Canvas (RxJS) by Ayumu Sato (@ahomu) on CodePen.
ahomu/demo-react-bacon · GitHub
See the Pen Draw Canvas (RxJS) by Ayumu Sato (@ahomu) on CodePen.
Reactive Programming in JavaScript
@ahomu / 2015.02.21 - Frontrend Final Conference
// カウンター値の保持 var current = 0;
// click されたら... plusEl.addEventListener('click', function(e) { // current を加算/減算して... current++; // innerHTML を更新する counterEl.innerHTML = current; });
minusEl.addEventListener('click', function(e) { current--; counterEl.innerHTML = current; });
// click で 1/-1 が流れるストリーム var plus = getClickStream(plusEl).map(1); var minus = getClickStream(minusEl).map(-1);
// 1/-1 のストリームをひとつにマージ var source = plus.merge(minus);
// ストリームから値が来ると現在値(current)を計算 var current = source.scan(0, function(acc, v) { return acc + v; });
// current の変更を subscribe して innerHTML を更新 current.subscribe(function(v) { counterEl.innerHTML = v; });
佐藤 歩 Ayumu Sato
Web Frontend Android Engineer
http://aho.mu
HTML5 Experts 幽霊部員
今日のアジェンダ
What is Reactive Programming?
Reactive in Frontend JavaScript
FRP with Reactive Extensions
What is Reactive Programming? イベントや値の関係性に 注目したパラダイム
イベントや値の関係性 = データフロー
データフローの宣言を元に変更を自動的に伝播させる
c = a + b なら c が示す値は a, b と共に変わって欲しい
current = accumulate(merge(plus, minus))
var plus = getClickStream(plusEl).map(1); var minus = getClickStream(minusEl).map(-1); var source = plus.merge(minus);
var current = source.scan(0, function(acc, v) { return acc + v; });
手続きの記述 → 関係性の宣言
データがどのような関係なのかに集中する Reactive なコードは 宣言的なデータフローを示す
実装はコーディング時の考え方とライブラリに依存する
OOP な言語やデザインパターンでも実現はできる
Reactive っぽい = データの関係性を宣言的に表しているか
RP 界隈はノイズが多い
いわゆる「広義」と「狭義」すら境界が曖昧
Reactive っぽさ (特に自動伝播) の手段は色々ある
各手段が Reactive を自称したりしなかったりする
ref. Reactive Porn - steps to phantasien
① Actor Model
Erlang Actor
Akka(Scala) Actor
並列・分散な処理システムにおける数学モデル
非同期な並列処理をいいカンジにする(乱暴)
② Functional Reactive
Haskell FRP Libraries
Microsoft Reactive Extensions
関数型のパラダイムを取り込んだ RP
GUI 開発で主流なのはこっち方面
③ The Reactive Manifesto
Reactive な Application の特性を述べた文書
Responsive - 反応性
Resilient - 障害耐性
Elastic - 柔軟性
Message Driven - メッセージ駆動
並列システム寄りとかインフラ向けで
フロントエンド的にはスルーしてもよさげ
④ Reactive Streams
Actor と Reactive Extensions の交差点が Reactive Streams
Node の Stream は似てるけど今の所は関係なさそう
ref. 2014 akka-streams-tokyo-japanese
ref. 非同期ストリーム処理の標準化を目指す "Reactive Streams"
Reactive in Frontend JavaScript JavaScript で UI を操作する
API からデータを受け取って HTML に反映
ユーザー入力を受け取って HTML に反映
イベントを受け取って HTML に反映
API からデータを受け取って HTML に反映
$.getJSON('http://example.com/api', function(res) { var html = ''; res.list.forEach(function(item) { html += '
ユーザー入力を受け取って HTML に反映
buttonEl.addEventListener('click', function() { targetEl.innerHTML = 'clicked!'; }, false)
イベントを受け取って HTML に反映
bbView.listenTo(bbModel,'change',function(model) { view.render(model); });
非同期で変化する状態を管理して 都度 UI に反映するコードを 書くのはダルいこと
すごくダルい、本当にダルい Reactive な仕組みが 自然と求められる
Reactive = 片方の変化を他方に自動で伝播する仕組み これまでの JavaScript の 中にみる Reactive Observer パターンは 初歩的な解決のひとつ
プログラムとして動いている仕組みは同じ
Reactive 的には Observer パターンを隠蔽して宣言したい
かつては Reactive な在り方のひとつだったのかも(?)
データバインディングは 局所的な Reactive
何らかの仕組みで View と Model を宣言的に結びつける
局所的だが GUI におけるリアクティブ要素のひとつ
ref. データバインディングとリアクティブプログラミング
React with Flux は Reactive なデータフロー
データを DOM へ効率よく伝播させるのはRPっぽい
1方向データバインディングを実現する手段の一種
細かい異論はある: ref. Don't React - webbisauna 14
人類によるこれまでの抵抗
コールバックを避けた非同期処理の抽象化
Promise 一族, Async.js などで平たくなるよう努める
UI への反映を容易にする抽象化
Data Bindings や Templating (vdom含) で雑にする
Promise による非同期処理の抽象化
// single promise.then(function(res) { andDo(res); });
// parallel and chain Promise .all([ promiseFn1(), promiseFn2(), promiseFn3() ]) .then(function(results) { return promiseFn4(results); }) .then(function(result) { andDo(result); });
データバインディングによる抽象化
Search: Sort by:
-
{{phone.name}}
{{phone.snippet}}
しかし
Promise (などの Flow Control)
Click のように離散的なイベントは扱えない
Data Bindings
View と Model を結びつけるための局所的 Reactive
非同期とか気にせず すべて同じように扱って 何でも Reactive に宣言したい
そんな夢を叶えるモデルが、5年以上前からあります FRP with Reactive Extensions 関数型プログラミング + リアクティブプログラミング
略して FRP (Functional Reactive Programming)
RP が関数型プログラミングを取り入れたもの
Event と Behavior という概念があったり (割愛)
FRP の GUI 的な代表格が Reactive Extensions
略して Rx ・ Microsoft .NET 方面の生まれ
Haskell 方面にある元祖? FRP と C# の LINQ の子供
JavaScript の主要な FRP ライブラリはこれの系統
Reactive Extensions シリーズ
ReactiveX/RxAndroid
ReactiveCocoa/ReactiveCocoa
Reactive-Extensions/RxJS
neuecc/UniRx
いずれも Reactive Extensions インスパイア系
どのようなライブラリなのか?
全ての値や入力を非同期データストリームとして見なす
入力に map や filter などの高階関数で処理を適用する
非同期データストリームを中心に、その繋がりと処理を記述する
クライアントサイドの処理は 時間軸の変化に依存する
ユーザー入力イベント
ディスク I/O
ネットワーク I/O
タイマー処理
ref. Functional Reactive Programming with Bacon.js 非同期データストリーム ( すべてを時間軸に沿って要素が連続するリストにする )
Fig
ref. Functional Reactive Programming with RxJS Events as List
Error, Complete
Single Value
From Array
Async Value
ストリームをリストと見なすことで 関数型のイディオムを 活かせるようになった
JavaScript は first-class function なので尚のこと RxJS
Reactive Extensions 謹製 JavaScript 実装 セッション冒頭の RxJS サンプルをおさらい
データストリーム (Observable) をどう扱っているか
/* +---------------+ | click.map(1) |---> +------+--------+
+------+--------+
| click.map(-1) |--->
+---------------+
*/ var plus = Rx.Observable .fromEvent(el, 'click').map(1); var minus = Rx.Observable .fromEvent(el, 'click').map(-1);
/ +--------------+ | click.map(1) | +------+-------+ | +----------> | +------+--------+ | click.map(-1) | +---------------+ / var both = plus.merge(minus);
@type {Rx.Observable} curtValue
/ +--------------+ | click.map(1) | +------+-------+ | +------------+ +---------->|scan(0, add)|---> | +------------+ +------+--------+ | click.map(-1) | +---------------+ / var curtValue = both.scan(0, function(acc, v) { return acc + v; });
@type {Rx.Observer} htmlSet
/ +-------------+ --->|innerHTML = v| +-------------+ / var htmlSet = Rx.Observer.create(function(v) { element.innerHTML = v; });
/ +--------------+ | click.map(1) | +------+-------+ | +------------+ +-------------+ +-------->|scan(0, add)|--->|innerHTML = v| | +------------+ +-------------+ +------+--------+ | click.map(-1) | +---------------+ / var subscription = curtValue.subscribe(htmlSet);
Rx.Observable の インターフェースが中心になる
Observable = 非同期データストリーム 絶望の数 (Observable Instance Methods)
amb, and, asObservable, average, buffer, bufferWithCount, bufferWithTime, bufferWithTimeOrCount, catch | catchError, combineLatest, concat, concatAll, concatMap, concatMapObserver, connect, includes, controlled, count, debounce, debounceWithSelector, defaultIfEmpty, delay, delayWithSelector, dematerialize, distinct, distinctUntilChanged, do, doOnNext, doOnError, doOnCompleted, doWhile, elementAt, elementAtOrDefault, every, expand, filter, finally | ensure, find, findIndex, first, firstOrDefault, flatMap, flatMapObserver, flatMapLatest, forkJoin, groupBy, groupByUntil, groupJoin, ignoreElements, indexOf, isEmpty, join, jortSort, jortSortUntil, last, lastOrDefault, let | letBind, manySelect, map, max, maxBy, merge, mergeAll, min, minBy, multicast, observeOn, onErrorResumeNext, pairwise, partition, pausable, pausableBuffered, pluck, publish, publishLast, publishValue, share, shareReplay, shareValue, refCount, reduce, repeat, replay, retry, retryWhen, sample, scan, select, selectConcat, selectConcatObserver, selectMany, selectManyObserver, selectSwitch, sequenceEqual, single, singleOrDefault, skip, skipLast, skipLastWithTime, skipUntil, skipUntilWithTime, skipWhile, some, startWith, subscribe, subscribeOnNext, subscribeOnError, subscribeOnCompleted, subscribeOn, sum, switch | switchLatest, switchMap, take, takeLast, takeLastBuffer, takeLastBufferWithTime, takeLastWithTime, takeUntil, takeUntilWithTime, takeWhile, tap, tapOnNext, tapOnError, tapOnCompleted, throttleFirst, throttleWithTimeout, timeInterval, timeout, timeoutWithSelector, timestamp, toArray, toMap, toSet, transduce, where, window, windowWithCount, windowWithTime, windowWithTimeOrCount, withLatestFrom, zip
via. RxJS/observable.md at master · Reactive-Extensions/RxJS
Transforming.Map
via. ReactiveX - Map operator
Rx.Observable.of(1, 2, 3) .map(function(x) { return x * x; }) .subscribe(function(value) { console.log(value); });
// => 1 // => 4 // => 9
Filtering.Filter
via. ReactiveX - Filter operator
Rx.Observable.range(1, 7) .filter(function (x) { return x % 2 === 0; }) .subscribe(function(value) { console.log(value); });
// => 2 // => 4 // => 6
Combining.Merge
via. ReactiveX - Merge operator
var a = Rx.Observable .fromEvent(el,'click').map('a'); var b = Rx.Observable .fromEvent(el,'click').map('b');
a.merge(b).subscribe(function(value) { console.log('From merged: ' + value); });
// => From merged a // => From merged b // => From merged b
Transforming.FlatMap
via. ReactiveX - FlatMap operator
Rx.Observable.of(1, 2, 3) .flatMap(function (x) { return Rx.Observable.of(x, x*x); }) .subscribe(function(value) { console.log(value); });
// => 1 // => 1 // => 2 // => 4 // => 3 // => 9
ErrorHandling.Catch
via. ReactiveX - Catch operator
Rx.Observable.of(1, 2, 3) .map(function (x) { if (x === 2) throw new Error(); return x; }) .catch(function (e) { return Rx.Observable .return(e instanceof Error); }) .subscribe(function(value) { console.log(value); });
// => 1 // => true
Rx.Observable.of(1, 2, 3) .map(function (x) { if (x === 2) throw new Error(); return x; }) .onErrorResumeNext(Rx.Observable.of(4, 5, 6)) .subscribe(function(value) { console.log(value); });
// => 1 // => 4 // => 5 // => 6
フローコントロールの用途で Async.js と読み替える例
RxJS/comparing.md at master · Reactive-Extensions/RxJS
これを参考にすると、フローコントロール的には分かりやすい
各 FRP ライブラリの概観
RxJS は ReactiveExtension 由来の正統派
Bacon.js は RxJS を参考にした独自コンセプト
Kefir.js は Bacon.js に似たAPIの軽量化版
Meteor は Tracker.js を中心にサバクラで共有できる Reactive
Elm は Haskell っぽいのから HTML/CSS/JS を生成する
Bacon.js
One of the main motivators for reactive-bacon has been the weirdness of RX with regard to "hot" and "cold" observables. In reactive-bacon, there are no such things. The EventStream is always consistent with respect to time, so there will be no WTFs from that direction.
via. raimohanska/reactive-bacon よくわからない人もいると思うので デモを見てみましょう! Demo 1 (RxJS)
Canvas にドラッグ操作で線を描画する RxJS でリライトしてみる
Draw Canvas (VanillaJS) ↓ Draw Canvas (RxJS) Demo 2 (React + Bacon.js)
ReactとBacon.jsを組み合わせて簡単なUIを作ってみる
スライドを送れなくなったひとは、ココを click してから操作してね ahomu/demo-react-bacon
https://github.com/ahomu/demo-react-bacon
スライドを送れなくなったひとは、ココを click してから操作してね ahomu/hn-react-rxjs
https://github.com/ahomu/hn-react-rxjs
実際に書いてみた感想
- Observable と Observer ごとにモジュール化するカンジ?
- React + Flux の Store 周りにはいらない気がする
- 割り切って UI コントロール用途で使うほうが幸せかも
- Bacon.js 系のほうが昨今のフロントエンドには混ぜやすそう
- ストリームの入出力をドキュメントで明示しないと大変
- 高階関数の命名規則を考えたほうが可読性上がりそう
まとめ
- RP はデータフローの関係性を宣言する
- FRP は非同期データストリームを扱うモデル
- RxJS は Observable を制すれば勝てる
- (自分は Bacon.js の EventStream/Property が好き)
- クセはあるけど触れてみると発見があって面白い
FRP を知るオススメ文書
- Reactive Porn ( 最初に読むのを本当におすすめしたい )
- なぜリアクティブプログラミングは重要か。
- 【翻訳】あなたが求めていたリアクティブプログラミング入門
- Functional Reactive Programming (よくまとまってる)