by shigemk2

当面は技術的なことしか書かない

Reactive Programming in JavaScript #frontrend

@ahomu

Reactive Programming in JavaScript

CAのひと

  • What is Reactive Programming?
  • Reactive in Frontend JavaScript
  • FRP with Reactive Extensions

What is Reactive Programming?

  • プログラミングパラダイムのひとつ
  • イベントや値の関係性について着目する

手続きの記述→関係性の宣言

宣言的なデータフローをちゃんと示しているか 実装はコーディングやライブラリに依存する。

RP界隈はノイズが多い

広義や狭義が曖昧

  1. Actor Model(並列処理 分散システムの数学モデル)

アクターモデル - Wikipedia

  1. Functional Reactive

  2. もとはHaskellのライブラリ

  3. 関数型のパラダイムをとりこんだRP

  4. Reactive Manifesto

The Reactive Manifesto

  1. Reactive Streams

http://www.reactive-streams.org/

ここまではすごくふんわりしている。具体的なコードもないし。

JSでUIを操作する

APIからデータを受け取ってHTMLに反映する

だるいからライブラリを使う→Reactiveな仕組みが自然と求められる

これまでのJavaScriptのなかに見るReactive

  • Observerパターンは初歩的な解決の一つ
  • 宣言→より隠蔽して抽象化したもの
  • データバインディングは局所的なReactive(ViewとModelを宣言的に結びつける)

  • React with FluxはReactiveなデータフロー

  • 一方向のアレを実現している

Don't React - webbisauna 14

  1. コールバックヘルを避ける
  2. UIでどうにかする

  3. PromiseはClickのような離散的なイベントは扱えない

  4. 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

f:id:shigemk2:20150221135649p:plain

Rx.Observable のインターフェースが中心になる

絶望の数(Observable Instance Methods)

変換処理がかかった次

f:id:shigemk2:20150221135931p:plain

(このあたりは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.

f:id:shigemk2:20150221141108p:plain

ahomu/demo-react-bacon · GitHub

See the Pen Draw Canvas (RxJS) by Ayumu Sato (@ahomu) on CodePen.

ahomu/hn-react-rxjs · GitHub

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 += '

  • ' + item.title + '
  • ' }); listEl.innerHTML = 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 (よくまとまってる)