ニートが学ぶUniRx Everything is a stream

どうもこんにちは、流れに乗れないニートです。
Everything is a streamというわけで、本日はストリームを感じてみたいと思いますよ。

全てはストリーム

これを書いている現在、僕はUniRxを学習し始めて四日目です。
「なぁにが全てはストリームじゃこのボケカスナス! ジェットストリームアタックでもしてろ!」
と怒鳴りたくなる衝動に駆られつつも、UniRxの学習を続けています。
さて「全てはストリーム」と言われていますが、そもそもストリームってなんでしょう。
僕がこれまで学習したところから察するに、要はObservableの言い換えなんではないかと思っています。
実際、UniRxはAsObservable系のメソッドが多いですよね。
これはまさに「ストリームに変換し、全てをストリームとして扱っている」からではないでしょうか。

ストリームを感じるには

しかし今のところ「これこそがストリームだ!」みたいな感じは全くありません。
同じことを感じている人もいるようで、以下の記事が見つかりました。
https://qiita.com/litmon/items/b3052ad38758086c83af
この人は「カウントアプリ」の作成を通じて全てをストリームに感じられたらしいです。
ちゅーわけで僕も真似します。

UniRxなしでカウントアプリを作る

まずはUniRxなしでカウントアプリを作ってみましょう。
記事中では一つのクラスに機能を詰め込んでいるようなので、僕も今回はそのように作ります。
というわけでUniRxなしバージョンをドン。

数分で書いたコードですが、特に問題なく読めますよね。
これくらいの機能なら確かにUniRxの出番なぞないようにも思えます。
しかしそれでは話が進まんというわけで、次はUniRxバージョンに挑戦しましょう。

UniRxを使って書き直す

記事では「Observable.OnSubscribe」なるものが使われていたのですが、恐らくこれはUniRxで言うObserverですかね?
なのでObserverを作成してみます。

そしてボタンが押されたときに何やらしないといけないので、その処理も加えたものも以下に一気に書きます。

これでとりあえずは動きます。
UniRxも使っています。
ですが「Count」プロパティは依然として存在していますし、なんかあんまりストリーム感がないですよね。
ちゅーか、ぶっちゃけ「見た目がややこしくなっただけ」にしか思えませんw
もう少しUniRxっぽく書きたいです。

どう直せばいい?

ストリームはオペレータを適用することができます。
こいつを利用してCountプロパティを消してみたいと思います。。
というわけでコードをドン。

見ての通りCountプロパティが消えました。
また、ObserverがclickObserver一つに統合されています。
これはObserverが実行する内容が単に「テキストに文字列を表示する」という機能に絞られたことに依ります。
代わりに各種Observableが複雑になっています。

plusObservableを見てください。
クリックされたとき、Selectオペレータによって値を数字の1に変換しています。
この変換された値をScanオペレータにかけていますね。
Scanオペレータは要はLINQのAggregateです。
JavaScriptならreduceですかね。
今回の場合ですと、アキュムレータに溜めている値と、流れてきた値を足しています。

minusObservableはこれを単に-1へ変換したバージョンですね。
また、この時点ではまだSubsribeしていないことに注目してください。

さて次に目に入るのは

ここですね。
CombineLatestの挙動の説明は以下のサイトの図を見てもらうのが早いかもしれません。
http://reactivex.io/documentation/operators/combinelatest.html
あるいは言葉での説明なら、以下のサイトの説明がとてもわかりやすいと思いました。
https://blog.okazuki.jp/entry/20120219/1329663635
「CombineLatestメソッドは、どちらか一方から値が発行されると、もう一方の最後に発行した値があるか確認し、あればそれを使って合成処理を行い、後続に値を流します。」

今回の場合なら
plusObservableかminusObservableから値が発行されると、もう一方の最後に発行した値があるか確認し、あればそれを使って合成処理を行い、後続に値を流す、ということですね。

さてゲームを実行してみると、プラスボタンを連打しても何も表示されないことに気がつくかと思います。
これはCombineLatestが「もう一方の最後に発行した値があるか確認」し、「なければ合成処理がおこなわれず、したがって後続にも値は流れない」ためです。

両方のボタンをクリックしないと値が見えないのは不便ですよね。
また、直感的でもありません。
というわけでSubsribeされた瞬間に初期値を与えてみます。

StartWithはSubsribeされたタイミングで初期値を流します。
これでバッチリ動きます。
試してみてください。

さらなる改善

もう少し改善できそうですよね。
例えばclickObserverって、これ必要ですかね?
内容も単純ですし、Subsribeするとき直接書いちゃってもいい気がします。

あとは.StartWith(0)が重複しているのですが、これはうまい方法が思い浮かびませんでした。
あとはなんかオブジェクトが破棄されたときストリームを停止させるのがいいらしいので、AddToもつけておきます。
というわけで以上を盛り込んだ完成形と、元のコードを見比べてみたいと思います。

まずは元のコードがこちら。

そしてUniRxで書き直した初期バージョン。

完成形もドン。

いかがでしょうか?
どれがわかりやすいですかね。
たぶん、慣れないと最後のバージョンは何をやっているのかわかりにくい気がします。
逆に最初のバージョンや、UniRxの初期バージョンは誰でも読めると思います。
この辺のバランスを取らないとダメなんですかねえ……。
それとも、もう少しやったら完成形の方が読みやすく感じるんでしょうか……。

終わりに

本日はストリームを感じてみたいということで、少し丁寧に作成過程を追ってみました。
Everything is a streamになっていることはなっていますね!

ほなそんな感じでまた。
あ、最近書いていませんでしたが、お仕事も募集中です。

フォローする