LoginSignup
6
6

More than 5 years have passed since last update.

Rxで再代入を無くそうとアレコレする

Posted at

前置き

再代入は怖いです。
タイトルに入れただけでマサカリ飛んできそうな気がします。

int x = 0;
x = 1; // さっき0だって言ったじゃん!ウソつき!

単なる変数ならまだいいですが、ユーザー入力とか絡むと最悪です。

そんな(時間)変化とかいうわけわからん物を包むためにRxなるものが現れました。素敵ですね。
カウンタなど入力値から出力値を作るのに副作用があっても、
In(t) -> Out(t)と、
時間の関数を受け取り、時間の関数を返しているのです。ほーら関数的」などと
屁理屈 素晴らしい発送の転換で回避できます。

しかし、私のRx力が足りなくて、せっかくRxを使っているのに再代入を使っちゃってます。
モヤッとします。
Scan()でガンガン殺しましょう。
状態変数を宣言せずとも状態を扱ってやりましょう。

なおScan()の内部実装とかは気にしません。いいですね?

カウンタ

こう書く人はいないと思いますが、
一応基本的なところから。

int count = 0; // <- 状態変数(こわい)
var counter = input.Do(x => count += x).Select(_ => count);

Scan()でこう書けます

var counter = input.Scan(0, (a, x) => a + x);

状態変数を宣言、再代入せずに、「ひとつ前の状態」を扱うことが出来ます。

リセット付きカウンタ

学びたての頃はこんな感じで書いちゃってました。

bool reset; // <- 状態変数(こわい)
var counter = input.Scan((a,x) => reset ? 0 : a + x);

リセットしたければ
reset = trueにして
inputに値を流して
reset = falseにします

カッコ悪い。

どうせならリセット入力もストリームとして扱いたいところです。
扱いましょう。
resetを特別扱いするからイカンのです。
inputresetScan()への入力と考えればいいのです。

var counter = 
    Observable.Merge(
        input.Select(x => new {Value = x , Reset = false}),
        reset.Select(_ => new {Value = 0 /* <- dummy*/ , Reset = true})
        ).Scan(0, (a, x) => x.Reset ? 0 : a + x.Value);

Either型みたいなものです。

こんな感じで、Scan()の入力と出力の型に複数の情報をもたせると
再代入を使いがちな処理を再代入無しで実装できます。

SampleLatest的なもの

sourcesamplerストリームがあり、
samplerの発行タイミングで、sourceの最終発行値を流してみましょう。

source.Sample(sampler).CombineLatest(sampler,(x,_)=>x)
をよく使っていたんですが、なんか動作が怪しいのでScan()で作ります。
(これの動作が怪しくないとは言い切れませんが。)

sampleLatest

var source = new Subject<int>();
var sampler = new Subject<Unit>();

var sampleLatest
    = Observable.Merge(
        source.Select(x => new { Value = x, Sample = false }),
        sampler.Select(_ => new { Value = 0, Sample = true }))
    .Scan(
        new { Now = 0, Next = 0, Sample = false },
        (a, x) => x.Sample ? new { Now = a.Next, Next = a.Next, Sample = true }
                           : new { Now = a.Now, Next = x.Value, Sample = false })
    .Where(x => x.Sample)
    .Select(x => x.Now);

//てすつ

source.Subscribe(x => Console.WriteLine($"source : {x}"));
sampler.Subscribe(_ => Console.WriteLine("Sample"));
sampleLatest.Subscribe(x => Console.WriteLine($"-- sampled : {x}"));

source.OnNext(1);
source.OnNext(2);
source.OnNext(3);
sampler.OnNext(Unit.Default); // 3
source.OnNext(4);
source.OnNext(5);
sampler.OnNext(Unit.Default); // 5
sampler.OnNext(Unit.Default); // 5
sampler.OnNext(Unit.Default); // 5

出力
source : 1
source : 2
source : 3
Sample
-- sampled : 3
source : 4
source : 5
Sample
-- sampled : 5
Sample
-- sampled : 5
Sample
-- sampled : 5

拡張メソッド化すれば使いまわせてニコニコです。

まとめ

Scan()の入出力に複数の値をもたせたりして、「以前の状態」を上手く扱いましょう。
ただあまりやり過ぎると読みづらくてしょうがないので
関数型言語に持ち込むときやネタとしてどうぞ。

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6