Whether FRP is Type? or is it Pipeline?
FRPとは何なのか?時間軸に対する値の変化をする型なのか、パイプライン処理を表現しやすくする処理なのか。 自分が何がわかっていないのかわからないので、とりあえずポエムで吐き出します。
—
FRPに関して調べてるといろいろな解説がされています。
- Q. (関数型)リアクティブプログラミングとは何ですか? http://postd.cc/what-is-functional-reactive-programming/
FRPを「“時間とともに変化する”値を表すデータ型」とシンプルに表現しています。
- なぜリアクティブプログラミングは重要か。 http://d.hatena.ne.jp/pokarim/20101226
リアクティブプログラミングは、「時間とともに変化する値」=「振る舞い」同士の関係性を記述することでプログラミングを行うパラダイムです。
- 2015年に備えて知っておきたいリアクティブアーキテクチャの潮流http://qiita.com/hirokidaichi/items/9c1d862099c2e12f5b0f
リアクティブプログラミングを言い換えるのであれば、Observerパターンを意識させず関係性のみを記述することともいえるでしょう。
どの解説でも言われているFRPの特徴が
- 時間とともに変化する値を表す型
- 関係性の記述
の2つになります。
時間とともに変化する値を表す型
- データ型 http://ja.wikipedia.org/wiki/%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B
データ型(データがた、data type)とは、コンピュータにおけるデータの扱いに関する形式のことである。データタイプとも。データ型は、プログラミングなどにおいて変数そのものや、その中に代入されるオブジェクトや値が対象となる。
wikipediaを見ると型は上記のように説明されています。
では、それを踏まえて実際にFRPのダイアグラムとコードを見てみます。
- 【翻訳】あなたが求めていたリアクティブプログラミング入門 http://ninjinkun.hatenablog.com/entry/introrxja
requestStream: --a-----b--c------------|-> responseStream: -----A--------B-----C---|->
(lowercase is a request, uppercase is its response)
var requestStream = Rx.Observable.returnValue('https://api.github.com/users'); var responseStream = requestStream .flatMap(function(requestUrl) { return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl)); }); responseStream.subscribe(function(response) { // render `response` to the DOM however you wish });
変数、もしくはその中に代入されるものと考えるとrequestStream
とresponseStream
がその対象に思えます。
つまりはStreamのことを指して「時間とともに変化する値を表す型」と言っている訳ですね。(そうだと思いたい)
ダイアグラムを見れば「時間とともに変化する値を表す型」という表現に納得です。
Promise
しかし、このコードを見ているとPromiseを思い出します。
var requestPromise = Promise.resolve(‘https://api.github.com/users'); var responsePromise = requestPromise .map(function(requestUrl) { return fetch(requestUrl).then(function(response) { return response.json(); }); }); responsePromise.map(function(response) { // render `response` to the DOM however you wish });
単一のデータに対して記述しているので、似ているだけなのかもしれません。 requestに複数の値を設定してみます。
var requestStream = Rx.Observable.from([‘https://api.github.com/users', ‘https://api.github.com/users/k-kinzal/repos’]); var requestPromise= Promise.then([‘https://api.github.com/users', 'https://api.github.com/users/k-kinzal/repos']);
どちらも同じように最初のrequest
を作る箇所で複数の値を設定することができます。(Promiseにはbluebirdを使っています)
しかし、実際にコードを何度か動かすと挙動が違うことがわかります。
responseStream.subscribe(function(response) { console.log(response); // -> repos, users }); responsePromise.map(function(response) { console.log(response); // -> users, repos });
Streamはレスポンスが戻ったものからsubscribe
を実行し、Promiseは両方のレスポンスが戻ってから順序を維持してmap
を実行します。(たぶんあってるはず)
つまり、Streamは単一の値に対して後続の処理が流れ、Promiseは集合に対して後続の処理が流れます。
FRPもPromiseも似たような表現になりますが、この違いが使い分ける場合の指針の一つになると思います。
副作用
FRPの特徴の一つとして作成したStreamに値を流すことができます。
public func notificationsStreamAtInterval(interval: NSTimeInterval, since: NSDate) -> RACSubject { let subject = RACSubject() Timer(interval: interval).start { self.client.getNotifications(since) { (notifications, error) -> Void in for notification in notifications as Notifications! { subject.sendNext(notification) } } } return subject }
事前にStreamを作成し、そこに値をpushすることでStreamに値を流し続けることができます。 しかし、ここでFRPは「時間とともに変化する値を表す型」であるということを考えると、Streamの状態が変化します。
Stream: --a-----b--c------------->
このStreamに対してdをpushすると
Stream: --a-----b--c-----------d-->
と、Streamの状態が変化する訳です。 これは関数型という意味では正直どうなんだろうと思ってしまいます。(関数型に関して勘違いしている可能性がある) とはいえ、この変化を許容しないと正直なところ魅力が激減します。 例えば良く例に出てくるユーザーアクションに対してバインドして(クリックとかマウスムーブとか)、それに対して関係性を記述するなんてことが出来なくなります。 そう考えると、ここは許容せざるを得ないのですが、気持ち悪さが残ります。
なんだろ。Streamに副作用を押し込めるから良いという話なんでしょうか。
型?処理?
もやっとしたので、pushしたときの実装を除いてみることにします。
Dispatcher.prototype.push = function(event) { if (event.isEnd()) { this.ended = true; } return UpdateBarrier.inTransaction(event, this, this.pushIt, [event]); };
inTransaction = function(event, context, f, args) { var after, result; if (rootEvent) { return f.apply(context, args); } else { rootEvent = event; try { result = f.apply(context, args); flush(); } finally { rootEvent = void 0; while (aftersIndex < afters.length) { after = afters[aftersIndex]; aftersIndex++; after(); } aftersIndex = 0; afters = []; } return result; } };
pushしたタイミングでイベントを発火しているだけですね。(たぶん) ということは、別にダイアグラムのように内部に値を保持して状態が変化している訳ではなさそうです。 これでちょっとすっきりしました。
と、思ったところでFRPは「時間とともに変化する値を表す型」ということを思い出すと、これは型なのか・・・?という疑問がわいてきます。 よくよく考えてみると特に型制約がある訳でもなく、pushしたら後続の処理が発火されるだけの処理で、これをプログラムの型のIntとかと同列に考えるのは何か違う気がしてきます。(実装都合の問題で思想的なところはまた別?)
つまるところ、これってパイプライン処理ですよね。
FRPってなんなんだろう・・・。
その他
- Eventでしか見てないから理解できない? Behaviorなら変わる?
- それPromiseで良いじゃんな例
- HTTP Requestとか
- パイプライン的処理は全般的にそう思う
- 循環させると複雑さが跳ね上がる
- Observerの悲劇再演
- 複雑に分岐するStreamは死ぬ
- 全てをStreamにできる
- 正気の沙汰と思えない
- 狂気の沙汰ほどおもしろい
- つまりStreamはおもしろい
- 狂気の沙汰ほどおもしろい
- 正気の沙汰と思えない
- 関係性という言葉に疑問
- 反応的に処理を書けるだけで別に関係性は記述してない(既存言語の限界?)