リアクティブとかファンクショナルとか言わないReactiveX入門

しばし見かけるReactive Extensions(ReactiveX, Rx)に関する説明の多くはファンクショナルだのリアクティブだのモナドといったキャッチーなフレーズを使っている。けれども、そういう方面に馴染みのない人を相手にして、そもそもの概念的に何を解決したかったものなのかという説明があんまりない気がした。ユースケースを伴った説明も局所解すぎて現実における使い道がわかりにくい、というか誰も彼もデータバインディングしか例に出さないのはどうなんだ。これはストリームの川?それは表現形態であって実態を表しているとは言い難いだろう。

放置していても誰も書かない気がするし、神秘的な霊験と共に語られても全く役に立たないし、自分の思考の整理と(幾らかは同僚への説明も兼ねて)書いてみることにする。

もしかすると.NET界隈あたりでは過去にやりつくしたネタの再生産かもしれないし、ラジオなどの非文章媒体で既にあるのかもしれないけれど、見つからなかったので、まあいいや。


プログラミングの手法としてイベント駆動でコールバックを呼び出すパターンが存在する、というのは周知の事実。これは「何かが変化したら、それが変化したことを任意の対象に対して通知する」のに役立つ。典型的なのはGUIプログラミング。ユーザーがクリックしたことがイベントとして通知され、それを受けて処理を行う。ネットワークリクエストの結果を受け取るコールバックも、レスポンスが帰ってきたという局所的なイベントを受けて動作する点で変わりはない。

プログラミング的にもイベントが飛んでくるという抽象化がなされている方がやりやすいことは、それなりにある。もしイベントがないと延々とポーリングをして状態の変更を確認し続けなければならない。面倒臭い。

ところが、この古典的なイベント処理には問題がある。イベントというのは本質的に非同期に突如としてやってくるものであり、それぞれがバラバラに飛んでくる(離散的に、と言い換えても良いかもしれない)ものであるのが殆どだからだ。ネットワークレスポンスの終端イベントは開始イベントの後にやってくる、という程度の順序はあるけれど、個々のイベント自体は、自信がどういう順序関係で発行されたのかを情報としては持ち得ていない。

イベントが全部バラバラに飛んでくる以上、「画面がリサイズされた後にクリックが起こった場合」というような状況を適切に処理したい場合、各イベントのユーザーであるプログラマは、自分でフラグを立てるなどをしてイベントの順序を管理するしかない。イベントAに対応するフラグA’を立てて、その状態でイベントBが来たらB’を立てて、A’とB’の両方がtrueであれば後続に処理をつないでいきetc。フラグが増え続け、破綻する日は近い。

イベントAとBが両方とも来たことを示すCというイベントを発行する、という手法もあるが、これはイベント粒度を細くすればするほどやることが増えていくし、各イベントが取り扱う変化の範囲で悩み始める。

このイベントが多すぎる・関係性を持ち始める問題は、わかりやすく例えれば、GUIがリッチになるにつれ問題となっていく。GUIの変更・ユーザーの操作それぞれが条件となり、次に起こすべき変更もさらに条件となっていく。しかも、これがモバイル端末の上ともなると、ネットワークの状態、バッテリーの具合、環境光の変化などが出てくる。クライアントではなくサーバー側であれば、クラスタ内のノードの死活状態やログの送受、広くはクライアントからのリクエストもイベントとみなすことができるだろう。混沌としてくる。

こうした状況に対してLINQ to Eventsとも呼称されるReactiveXが提示した解決策は、イベントは離散的なデータの集合であり、それらから任意のイベントの関係性を取り出す方法があれば良いということ。度々例示してきたように、一見バラバラで独立している個々のイベントにも、実際に利用する際には関係性が存在している。その関係性をコードとして明示しクエリとして処理することで、コードを通して人間にも読める形式で、機械的に結果を取り出すことを可能にした。この結果、複数のイベントの関係性から別のイベントを生成することが容易になり、イベントに紐づく変化の範囲についてコードを書く側が悩む必要もなくなった(必要に応じてイベントを細かく割りやすくなった)。

これと同時にスケジューラという概念を導入することで、取り出したイベントを後続に伝えるタイミングや、イベントを発行する対象のスレッドなども一連の流れの中で制御できるようにした。

それと、イベントを文字列やsymbolのような固定値ではなくオブジェクトとし、構文ないしメソッドの形式でイベントの関係性を表現したことで、コード解析技術を通してイベントの関係性の解析・図示や入力補完といったことが可能になった。これにより、アプリケーション規模が巨大になっていってもIDEなどを通してコードを掌握しきることができるので、ある種のgoto jumpであったイベントハンドリングがコード上に静的な形式で落とし込まれることになった。

私のブログを読んでいる人はJavaScriptの読み書きをする人が多いと思うので、JavaScriptのPromiseとの関連で説明しておくと、Promiseは原則として1回しか自身の状態を変更できない = 1回限りのイベントにしか使えない(なので、実質的にはpull型で動き始める)。何回でも発生しうるイベント全般に対する適用ができないから、Promiseだけではイベントの関係性は表現し得ない。

さて、先述のような抽象化を実施するために、Rxの実装の裏側では多くの一時オブジェクトを生成するなどしているため、実行時のコストがかかるように思える。だが、ハードウェアの進歩とコンパイラVMの進化、及び抽象化に伴うプログラミング時の生産性の向上と合わせると、アプリケーションのレイヤーではコストとして無視するという選択が可能になった。コストが無視できない環境では従来通り頑張って書けばいいだけで、事態が悪化したわけではないし、言語そのものを乗り換える(JS -> Java/C# -> Cpp)ことでも案外解決できたりする。

また、イベントを発行することに伴うプログラミング時の負担が減った結果、状態の変更をイベントとして気軽に通知しやすくなった。これにより、ある意味では副作用を起こしやすくなった。副作用による問題の一つは、ある状態に依存している(ある状態であることを仮定している)後続の処理群が、前提条件の変更を適切にハンドリングできない点にある。故に、気軽に状態が変更された事実をイベントとして伝え、再度計算し直すことで、全体としての整合性を保てるようになる(だからと言って、気軽に手当たり次第に副作用を起こしていいかというとそれは別)(再計算によるコストも、前述の通り実行環境の進化で無視できるという前提に立っているので、無視できない変更が起こるケースは再計算せずに済ませるようにした方がいい)。

ReactiveX.ioのイントロに「俺たちはFunctional Reactive Programmingじゃない」って書いてあるのは、各自読んでもらうとして、だいたいこんな感じ?

ソフトウェアの設計に思いを馳せた何か

http://www.slideshare.net/saneyuki/my-thoughy-about-beyond-flux

(主にGUIの)アプリケーション設計的なものをぼんやりと考えていた時に、色々思いついたことを書いた。特にどこかで発表するという予定もないんだけど、作ったからには手元でタンスの肥やしにするのも勿体無いので貼っておく。「まあ、そうだよね」とかそういう感じでしかないし、何か真新しい内容があるわけではない。

結構散漫な話なので上には書かなかったんだけれども、ソフトウェア開発において人はよくベストプラクティスを求める傾向にあるけれども、現実には「悪くはない」程度の解を選んだり、バッドプラクティスやアンチパターンを避ける方が総じて上手くいくように感じている。理想形を探り求め続ける限りに天井は無いが、「良くも無いが、最悪に面倒臭いことは回避している」であれば下限が決まっているので、程々に上手くやれるという認識でいる。

それと、

ソフトウェアを設計していていくつかの設計案のうちどれがいいのか決めかねるようなときは、「とりあえずOSがやっていることをまねる」というのが良いノウハウな気がしている。だいたい我々が直面するような問題というのは、OSがすでに直面していて何らか解決されている。

という話は真であると思う。

最近のJavaScriptはスクリプト言語のジャンルで必要な機能がだいたい揃った気がする(言語的な範囲では)

少し前にTypeScriptと素のECMAScriptとfb::JSXを混ぜてcompile -> linkする記事を勤務先のブログに書いたのだけれども、それ以後に色々思った小話を一席。

先述のエントリで示した構成を趣味プロジェクト含めて2~3度試した結果、現代のJavaScriptは書き捨て的なスクリプトから、(漸進的な)静的型付けまで、言語機能的なところではソフトウェアの規模に応じて、自由にスケールアップできるという実感を得るに至った、という確信を得た。インクリメンタルに移行できるので新旧コードの混在もできるし、素のJSじゃないとできないものは、それにやらせればいい。これはTypeScriptなりbabelといったcompilerやAST操作ツール群の成熟と、npmエコシステム、ES6というベースラインの更新が絡み合っている。

こうしてコードベースの規模に応じて自由にあれこれできるのは非常に楽だなと思う所存。それに今のJavaScript処理系(Chakra/SpiderMonkey/V8/JavaScriptCore)はどれも相当に速いので、速度が直接的にボトルネックになるよりも、コードや設計のマネージの方が主題になることの方が多いと思う(これは計算量がワーストケースに落ちない戦略・設計的なマクロなアプローチを含む)。micro optimizationが必要なケースもあるだろうけど、だいたいは

  1. ボトルネックを計測
  2. 頑張る
  3. 頑張って無理なら諦めてもっと速い言語を使う

な感じになるだろうし。

TypeScriptの提供する以上の言語機能が欲しい場合はどうするか?という問題は、素直に言語を乗り換えるのが良いのでしょう。あれより高水準な表現力とか静的型付けを持つ言語はだいたいJVMで動く言語かネイティブコードを吐いて動かす言語になるし、それが必要になる場合は速度面でもJavaScriptの適応可能範囲を超え始めるだろうから、乗り換えの時期なのだろう。

例外として、TypeScript以上の型表現を持ったAltJSというとScala.jsとかがあるけれども、各処理系のfirst tierがECMA262である以上、なるべくそれに近い方が色々と予測性が高いのとTypeScriptも1.6な今ならあれで多くのケースは解決すると思うので、私はあまり興味がない。大抵のケースは、Scalaが欲しいなら素直にon JVMな実装使った方がいいと思う。JSVMの上で語るにしても、WebAssmebly(のGC intergration)が来てからじゃね?

話が逸れたけど、Closure Compilerとかに頼らずとも、大きなソフトウェアを作るまでの言語機能のスケールができるようになったのは、いい時代になりましたね、という趣。

RxJSとRxJavaとでは旨味のコクが違うように思う

Rx(もしくはReactive ExtensionsないしReactiveX)には

  • Observable
  • Operators
  • Subject
  • Scheduler

と云った概念がある(分類はReactiveX.io調べ)。

で、Rxを用いた非同期処理を実際に行うにあたっての実行計画を制御するための要素が Scheduler に当たり、実行タイミングや実行スレッドの制御をこれで行うことでRxはLINQ to Events足り得ており、時間軸に関係するイベントもテスタブルにしている……みたいな話はRxをかじった人なら知ってると思います。かじってない人はそんなもんだと流してください。

で、ここしばらくRxJSでいろいろやっていたのだが、このSchedulerの重みが、JavaC#とは全く違うのであろうという仮説を抱くに至った。

メモリ空間共有型のスレッドモデルを持つJavaC#に於いては、非同期処理・並行処理を行うにあたってはスレッドを適切に掌握する必要が有る。で、RxはSchedulerがあるおかげで、この面倒臭いスレッドの概念もそれなりに統合して取り扱えるようになっている、というのが売り文句の一つ。

一方のJavaScriptにはメモリ空間を共有するようなスレッド機能はなく、あるのはshared nothingなWorkerであり、基本はシングルイベントループをぶん回し続けるモデルになっている。そして、多くの処理は(特にNode.js方面のAPIは)非同期を前提としたAPIモデルとなっている。

故に、RxJSが提供するSchedulerは、同一スレッド上での実行タイミングの操作とテストダブルであり、スレッドをまたぐ処理への抽象化を加えるようなインパクトは無い。

なので、JavaScriptにはPromiseもあるしSchedulerのインパクトも無いし、JavaC#の場合に比して「冒険してまで導入したい」成分は薄れているように思った。人間、華々しさや煌びやかな様子に負けて手を出すのは常なので、インパクトが無いと起爆剤が欠けるよなあ。

breaking changeの整理

生きているソフトウェアに付き物の破壊的変更(breaking change)であるが、一口に破壊的変更と言っても、実際は複数の意味を包含している。 なお、「生きているソフトウェア」では主語が大きすぎるので、ライブラリであったりランタイムについての話とする。

一般的に破壊的変更という場合、以下のどちらかとなるだろう。

  1. セマンティクス・挙動の変更を伴う変更
  2. セマンティクスの変更を伴わないが破壊的とみなされる変更
    1. APIの名前が単純置換された

1は明らかに破壊的である。ソフトウェアに対して少なからぬ書き直しが発生するし、単純置換では済まないケースの方が多い。 そもそもの設計思想やアプローチに修正が加えられたので、前と後とでは違うものと言っても過言ではなく、十分に破壊的と呼ぶに足りえる。

しかし、2のケースは一概に破壊的と言えるかは怪しい。辞書的には破壊的だが、我々の思い浮かべる大規模な書き直しを強いられるような破壊性が あるわけではないからだ。

2を破壊的と呼ぶか否かは、対象となる環境の置かれたバックボーンに依存することとなる。例えばRustのような強い静的型付の言語では、 コンパイル時に静的解析が発生するため、APIが消えただの名前が変わっただのは只のコンパイルエラーとして処理される。 標準ライブラリに絡む変更である場合はコンパイラが警告を細かく出してくれることもあるだろう。この場合、破壊的なリスクは早期に発見できるので、 相対的に破壊的変更は軽度の問題と化す。

逆に、実行するまでわからないような言語環境(典型的にはJavaScriptが該当する)では、このリスクは格段に跳ね上がる。 動かさないとわからない上に、実際にそのパスに入って実行されないわからない変更である場合、「やってみるまではわからない」ブラックボックスと化す。 エラーで落ちれば御の字だが、実は上方のtry-catchがエラーを握りつぶしてしまっており、前提条件が崩れたままに後続の処理に入り……などいう袋小路に入ると本当にどうしようもない。 この場合の破壊的変更のリスクはセマンティクスの変更と差はない。なので、我々はこれを早期に発見できるように頑張る必要がある。 semantic versioningは絶対遵守されるべきである。道徳律を超えた規範にして、これに逆らうライブラリ作者は異端審問で火刑に処されても構わない…というのは、やりすぎだが、 こうした自身のソフトウェアの破壊的な挙動につながる事態は想定されるべきとなるし、それを避けるためにテストコードの充実やカバレッジといった指標の整備、 CHANGELOG.mdを記述する道徳律の作成コミュニティの規範と正義を整えて共同体秩序を守る必要がある。かなり面倒くさい。できればあんまりやりたくない。

ここらへんの文化圏の交差とか色々残りはあるんだけど、書いてる最中にやる気がなくなったので寝ることにする。おやすみなさい。

Re: Rx.js, Immutable.js について

Rx.js, Immutable.js について - mizchi's blog

ファイルサイズへの異論は無い。これを使わずとも解決できる問題があるというのも同意する。それでもイマイチ釈然としない。「使う必要のない」一般公理と「使いたくはない」私情の境界が曖昧に感じてしまう。仕方がないので反論を兼ねた個人の雑感を書いてみる。いわゆる、「ついカッとなってやった」に近い。

RxにせよImmutableJSにせよ、この手の標準データ構造を提供するライブラリは基盤性が強いので、全体的使うことでバイナリサイズに見合っただけの成果をもたらす系のライブラリに区分される。そのため、局所的に使いたい場合はあまり意味がない。この辺りはReactと類似してる面がある。

使うなら上から下まで、 あるいはオニオンアーキテクチャ的な例えで言うならある階層から外側のインターフェースを統一するようにしないと、さして美味しくはない。抜け出そうと思って数時間で抜け出せる程度にしか使わないのであれば、味はでてこない。全てのイベントをファーストクラスオブジェクトとして「その上に乗せる」のがRxの狙う抽象化であるし、collection操作をImmutableにすることで、破壊的操作からの解放やshallowな比較を用いることでのcollection同士の比較の高速化を企図するのがImmutableJSである。

また、多層構成の設計を組む場合、たとえばRxであれば外部からの入力をadapterのレイヤーで抽象化しきる(本来adapterでやるべき内容を別のレイヤーに漏らさずに済む)ことができるので、多層戦略を維持するのに向いている。反面、そもそもコードベースが小さかったり、設計も単純なケースだと要らぬ抽象化を増やすので、入れないほうが楽なことも多いだろう。

学習コストという単語もざっくりしすぎではないか。それはパラダイムの学習コストなのか、APIの学習コストなのかを分けて話すべき。パラダイムの学習コストが高いという問題は、学習する本人と、ライブラリによりますね以上のコメントは避けておきたい(それ以上のことを一般化していうのは難しい)。

だが、APIの学習コストが高いという問題は

  1. よく使うものだけを覚える
  2. 全部覚える
  3. IDE支援を使う

のうち、1~3の組み合わせで解決するものなので、どれを選択できるかによる。最終的には多かれ少なかれ、よく使うものを覚えることになってしまうとは思うが、そこそこ使えるようになるまでの取っ掛かりとして考えてみる。IDEに頼るのが楽だが、それをするには静的型付けを使ってIDEにコードを解析させやすくする必要があり、やっぱり静的型付けがあったほうが(IDEにとっても)嬉しいねという話になる。TypeScriptと組み合わせるとRxを書くのが楽になるのはそれが理由。Visual Studio Codeとかがあるわけだしね。JSDocをきっちり書けば補完精度も上称する類のIDEなら、AltJSまで行かずとも良いだろう。

ライブラリ固有の弁護としては、 ImmutableJSは、原則としてcollectionの提供であり、OrderedもしくはそうでないSet/MapのAPIの延長線に位置する。 RxのAPIの多さは弁護しようがないんだが、あれはLINQ to Eventsなので、非同期なイベントの集合に対してだいたいArrayに似たcollection操作(というかクエリというか)をするのがコンセプト。なのでmap/filter/flatMapあたりを覚えればだいたいなんとかなる。これに+して非同期操作なので、待ち合わせ系のメソッドを2~3覚えれば良いだろう。Promiseの連続版であるので、これも all/some/raceに相当するメソッドを覚えれば良い。これで概念としては6つ。実際にはマイナーバリエーションがあるので開始地点で10前。新しいcollection型が登場したと思えば、なんとかなる量。覚える必要のあるAPIの数だけで言えばReactと大差はない。

(以後、私がRx好きなのでRxに関する話が続く)

もっとも、RxのAPIの数は、静的型付け言語 + 世界最高峰のIDEであるVisual Studioを使ってコードを書く前提が存在するC#由来なので、動的型付け + テキストエディタで書くのが主流のJavaScript文化圏では扱いにくいのは仕方がない。 さらにシングルスレッド環境下ではSchedulerなども使うことはさしてないし、そもそもJavaScriptにはPromiseがあって非同期処理のデファクトが出来上がっているので、わざわざRxを導入しなくても「なんとかなる」 ケースが多い。故に、あまり美味しさを感じないのは事実。逆にAndroid界隈でRxJavaがウケているのは、JavaC#と近いし、Javaがマルチスレッドという以上に、Android Javaが古くPromise相当のデファクトが無いから、という話を聞く。

1画面内で紐づく20の相関の無いイベントストリームを管理するというコストにしても、それはswitch + enumでやるか、それともObservableで経路を分割するかの話でしか無いので、あまりコストになるとは思えない。私の私見だと、巨大なswitch文を管理するのは避けたいので、enumで定義した数が多い場合は、そもそも経路が分かれていると嬉しいし、イベント間の待ち合わせが起こった場合に、そういう待ち合わせを扱える仕組みのある方が嬉しい。

JavaScriptの文化は標準化されたものが上がりを迎える世界なので、「仕様化されてないデータ構造は使いたくない」という話もわかるにはわかるが、specのpolyfill以外のライブラリは全部そんなものであるので、ライブラリ固有の問題とも思えない。

(パフォーマンス的な話にも言及しておくと、PromiseしかりObservableしかり、抽象化の代償にオブジェクトを生成して引き回すモデルになるので、抽象化を捨ててもオブジェクトの生成や余計な関数コールを避けたいような、エクストリームなハイパフォーマンスなプログラミングにおいては使わないという話はあるけれど、そんなことを言うのは今更だよね……)

まとめると

  • パラダイムが難しいかは、人とライブラリの組み合わせによりますね
  • APIが多い問題は道具で緩和が可能
    • なので静的型付け + IDEがある環境では導入しやすい(TypeScript + IDEのような環境だと使いやすい)
  • 基盤性の高い道具は、全体最適な道具として使わないと美味しくないし、コードベースが小さいうちは逆に邪魔になるのは正しい
    • 抜け出すのに最低数日〜かかるようになってからが本番

みたいな辺り。 私も結論としてはそこまで変わらないんだけれども、ポジティブに捉えているか否かはあると思う。