💏

RxSwiftのscan関数を利用して、RxJsに実装されているpairwiseを実装しなおしたよ

に公開

先日、会社でコードを書いていたときに、RxJsにあるpairwiseの機能を使う必要があり、実装したのでご紹介します。

RxJsのpairwiseとは

RxJSのpairwiseとは、「1つ前のイベントを最新の値と一緒に運んでくれるOperator」です。

公式の下記HPにマーブルダイアグラムがあるので、わかりやすいと思います。

https://rxjs.dev/api/operators/pairwise

ある特定の値が流れるストリームがあったとき、最新の値だけではなく、1つまえの値の状態も考慮して処理をしたいときにこの関数が便利です。

RxSwiftにpairwiseはないのか

結論、ありますがRxSwift本体には組み込まれていなさそうです

NSObject+Rxとかをコミュニティで提供しているRxSwiftCommunityのRxSwiftExtで提供されています。

https://github.com/RxSwiftCommunity/RxSwiftExt/issues/42

https://github.com/RxSwiftCommunity/RxSwiftExt/blob/eb4adf9f00a21b3efc3869a5218a6d7517e95222/Source/RxSwift/nwise.swift#L53-L56

多分これを使えば、nwiseの機能とセットで使えますが、この機能を使うためだけにRxSwiftExtをimportするのもアレなので自作しちゃおうと思いました。

実装

そんなに難しくなくて下記感じでOKです。
equatablePairwise は distinctUntilChanges しているときに必要なのでついでに実装してあります。

extension Observable {
    struct Pairwise {
        let previous: Element
        let current: Element
        
        func update(with newValue: Element) -> Self {
            .init(previous: current, current: newValue)
        }
    }
    
    func pairwise(initialValue: Pairwise) -> Observable<Pairwise> {
        return scan(initialValue, accumulator: { accumulator, newValue in
            return accumulator.update(with: newValue)
        })
    }
}

extension Observable where Element == any Equatable {
    struct EquatablePairwise {
        let previous: Element
        let current: Element
        
        func update(with newValue: Element) -> Self {
            .init(previous: current, current: newValue)
        }
    }
    
    func equatablePairwise(initialValue: EquatablePairwise) -> Observable<EquatablePairwise> {
        return scan(initialValue, accumulator: { accumulator, newValue in
            return accumulator.update(with: newValue)
        })
    }
}

scan と mapのちがい

このとき、値の変換をするだけならscanじゃなくてmapでもよいのでは?と思ったのですが、scanには状態を蓄積するというメリットが有るようなので、これを使いました。

RxSwiftにおけるscanとmapは、どちらも「値を変換する」という点では似ていますが、**「過去の状態を覚えているかどうか」**という点が決定的に違います。

一言で言うと、**mapは「単発の変換」、scanは「蓄積(アキュムレート)される変換」**です。

今回のパターンで言うと、「1つ前の状態という、状態の蓄積が必要」なので、scanが必要だったということですね。

おわりに

これをStructured Concurrencyでもやりたいんですが、あんまり良くわかってません。

色々調べて記事にできたら公開します。

Discussion