「Rx(Reactive Extensions)」と呼ばれる、observerパターン*1を非同期的/LINQ的に使用できるライブラリ(というかフレームワークというか)があります。
Rxは非常に高機能で完成度が高く、様々な言語に移植されているのですが、土屋にはこれまでイマイチ使い道がピンと来ていませんでした。「Rxはリアクティブプログラミングを実現する為の物」的な解説もありましたが、その「リアクティブプログラミング」がなんなのかよくわからない。
そんな折、LINEで太一さん(https://twitter.com/ryushi)にRxの役割について教えてもらって、ようやくRxなる物が腑に落ちまして、せっかくなのでその時のやりとりを記事に起こてみました。土屋のようにイマイチRxがピンと来てなかった人にオススメします。
注意
読みやすくするため、元の会話の順序を入れ替えたり、セリフを変更/追加している箇所が多々あります(そもそも、元々は3人の会話だった物を2人の会話に編集しています)。太一さんには文章をチェックしてもらっていますが、文責は土屋にありますので、以上了解の上お読みください。
導入:Rxって難しい割に使い道が良く分からないと質問
土屋「これ読んだのだけど つ https://qiita.com/king29/items/39b5d7c61810f274768d」
qiita.com
土屋「Rxが非常に高機能であることは分かった、と、思う」
土屋「けど、みんなホントにこんなややこしい物を使ってるの?」
土屋「土屋が大規模/非同期なアプリを作ってないというのもあるのだろうけど、ユースケースがイマイチ想像出来ない」
土屋「商業のUnityゲームアプリは大抵、RxのUnity実装*2を使ってるけど、開発チーム全員にRxの複雑な挙動を把握してもらってでも使う意議があるって事なの?」
太一「スマホのアプリなんかはみんなRx使ってるよ」
太一「Rx無しにまともに動くUI作るの考えられないよ」
土屋「そうなの……?」
Rxの成立経緯
太一「そもそもRxは、Silverlight*3に搭載されてたGUIのイベント処理をするためのライブラリ*4なのよ」
太一「それが一旦廃れて、Netflixの人達がサーバで使うためにJavaで再実装*5したあたりで復活してきた」
太一「その後、Androidでも使われるようになって、iOSでは今や標準ライブラリに入ってる*6」
土屋「『.NETで作られたライブラリであるRxが、廃れたり復活したりしつつ各言語に広まった』というのもよくわからないんだけど、」
土屋「observerパターンを非同期的/LINQ的に使いたいという需要が元々あって、それまでは各自でオレオレライブラリを作ってたけど、Rxライブラリの完成度が高かったから、それが各言語に移植されていったってこと?」
太一「そうだね」
土屋「なるほど」
イベントストリーム
太一「要は、『無限に連なるイベントをどう表現するか?』って話なのよな」
土屋「ふむふむ」
太一「既存のイベントリスナーモデルって、あくまで単発のイベントを処理するだけじゃん」
土屋「そりゃまあ。イベントリスナーモデルってそういう物でしょう?」
土屋「あるイベントが発生したら、対応するリスナーのメソッドが実行される、という」
太一「そう」
太一「でも実システムでは、連続で発生するイベントをストリームとして処理したいケースが多い」
太一「そういうのを既存のイベントリスナーモデルでシンプルに実装するのは面倒」
土屋「たとえば?」
太一「単純な話、『決定ボタンを連打された時に、2回目以降を無視する』ってコードを綺麗に書くにはどうする? って話よ」
土屋「お? なるほど?」
土屋「その場合、決定ボタン側で2回目以降のイベントの発生を抑止させるか、リスナー側で2回目以降に受け取ったイベントを無視させる必要があるな?」
太一「そうだね。この場合、個々のイベント単位ではなく、全体をイベントストリームとして処理しないといけない」
太一「Rxなら、それをすごく自然なコードとして、かつグローバル変数を使わずに書ける*7」
土屋「ははあ、なるほど。それがRxの役割なのか」
太一「で、そういうイベントストリームってのは、実システムのそこらじゅうにあるんよな」
太一「同じボタン制御でも、『ボタンを連打しても良いけど、5フレ以内に来たやつは無視する』とか」
太一「あるいは、『連打されたボタン分だけイベントを一旦予約しておいて、サーバと通信しつつ成功したら残りは捨てる。失敗したら予約しておいた分だけリトライする』とかね」
土屋「なるほどなー」
既存のイベントリスナーモデルからRxへ
太一「既存のイベントリスナーモデルと比べて、Rxは問題の適用範囲が圧倒的に広いから、一見するとすげー難しそうに感じるんよな」
土屋「確かに」
太一「でも、一度理解すると、もう戻れなくなる。既存のイベントリスナーモデルで書いてたコードの方がよっぽど難しかったなって感じるようになる」
太一「イベントリスナーモデル自体は簡単な概念なんだけど、簡単過ぎて、実システムではそのモデルの外側で考えないといけないことが多すぎるのよ」
太一「Rxを使えば、それらが全て統一された概念の元で扱えることが分かる」
太一「なので、Rxは極めて侵襲性が高くて、一度使い始めるとあらゆる場所で使いたくなる。既存のイベントリスナーモデルとRxの間で整合性を取るのはクソダルだからね」
土屋「Rxベースのシステムでは、イベントストリームを主体として設計されるようになるわけだ」
土屋「個々のイベントのやり取りの管理から、それらのイベント群が流れる川の方を管理するようになる」
太一「そういうことです」
Rx以前、以後。「Rxはイベントストリームが主体」
太一「Rx以前は、ボタン押すのと、デバイスの時刻監視と、サーバとの通信とは、全部違う独立したものだった。しかし、Rx以後は全てがイベントストリームになるから、簡単に混ぜられるし、分離できる」
太一「『サーバからデカいファイルをダウンロード中に、500フレ毎に100フレだけキャンセルボタンの入力を受け付けつつ、ダウンロード済みのバイト数に応じてインジケータを増やしていく。』みたいなの、誰もが書いてるコードじゃん」
土屋「無限に書いてるやつ」
土屋「Rxはイベントストリームが主体だ、というのは目から鱗。解説記事を読んでもそういう物として認識できてなかった」
土屋「ネットのどこかに「Rxはイベントストリームが主体」と刻んだ石碑を建てておいて欲しい」
太一「wwwww」
記事に起こすぜ
土屋「今日のこの会話、適当に編集してブログに上げてもいい?」
太一「いいよ。一応中身は確認させてね」
土屋「おけー」
おまけ
太一「https://rxmarbles.com/」
土屋「これは、Rxのイベントストリーム制御を図示した物?」
太一「そういうこと」
太一「Rxを理解するなら、まずこのダイアグラムをある程度丸暗記してから、説明文を読んだ方がいい」
太一「コードや文章だけだとイメージがわかないから難しく感じるのよ」
土屋「ぐぬぬ」
参考リンク
リアクティブプログラミング
Rxについての包括的な記事。更にステップアップしたい人向け。
ninjinkun.hatenablog.com
なお、記事内で「FRP(Functional Reactive Programming:関数型リアクティブプログラミング)」となっている記述は全部「RP(Reactive Programming)」と読みかえて大丈夫です(と、記事末尾にも書いてある)。
UniRx関連
UnityでのRxの使い方がわかる。LINQの挙動とUniRxの挙動を比較しながらの解説がわかりやすい。
annulusgames.com
UnityでのRx利用は「まずEveryUpdate()でイベントを毎フレーム発生させ、それをLINQでフィルタリングしていく」という感覚から掴んでいくのがポイントっぽい。
*1:https://ja.wikipedia.org/wiki/Observer_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3 ここではイベントリスナーモデルと同じ意味で使っています
*2:UniRx https://github.com/neuecc/UniRx
*3:マイクロソフトが開発したウェブブラウザ用のプラグイン。.NETでコンテンツを開発できた。2012年に最後のアップデートがあり、2021年にサポートを終了した。
*4:Rx.NET https://github.com/dotnet/reactive
*5:RxJAVA https://github.com/ReactiveX/RxJava
*6:Combine https://developer.apple.com/documentation/combine
*7:補足:非Rx環境で、システム全体でイベントストリームを共有/管理する場合、イベント単体のスコープより広いスコープの変数(例えばグローバル変数)を用意する必要があり、その変数の状態管理も必要になる。