hataaaaa’s diary

個人用メモが中心です

arrow-ktを使ってRxのSingleでflatMapのネストをなくすのをとりあえず試してみる

サンプルコードをもとに適当に1時間くらいさわってみたメモ。 強い人に刺されるの怖い

RxJava2のSingleを非同期処理とかに使っていると、データをくっつけたりする際にflatMap地獄になるときがたまにある。

たとえば hogeRepository, piyoRepository, fooRepository, barRepository があって、それぞれのRepositoryが直前のRepositoryの結果を引数にとり、全結果をまとめて返す処理を書きたいとする。

とりあえず愚直に書いたとき

// repositoryは全部Singleを返す
hogeRepository.find().flatMap { hoge ->
    piyoRepository.findByHoge(hoge).flatMap { piyo ->
        fooRepository.findByPiyo(piyo).flatMap { foo ->
            barRepository.findByFoo(foo).map { bar ->
                Result(hoge, piyo, foo, bar)
            }
        }
    }
}

みたいになり、まあまあ辛い気持ちになる。

Pair使ってみる

// repositoryは全部Singleを返す
hogeRepository.find().flatMap { hoge ->
    piyoRepository.findByHoge(hoge).map { piyo ->
        Pair(hoge, huga)
    }
}.flatMap { (hoge, piyo) ->
    fooRepository.findByPiyo(piyo).map { foo ->
        Pair(Pair(hoge, piyo), foo)
    }
}.flatMap { ((hoge, piyo), foo) ->
    barRepository.findByFoo(foo).map { bar ->
        Result(hoge, piyo, foo, bar)
    }
}

雰囲気で書いたけどネストの深さは抑えられた。けどもうちょっとなんとかしたい。

本題

arrow-kt を使ってみる

arrow-kt.io

arrow-ktを使ったとき

// repositoryは全部Singleを返す
SingleK.monad().binding {
    val hoge = hogeRepository.find().k().bind()
    val piyo = piyoRepository.findByHoge(hoge).k().bind()
    val foo = fooRepository.findByPiyo(piyo).k().bind()
    val bar = barRepository.findByFoo(foo).k().bind()
    Result(hoge, piyo, foo, bar)
}.value()   // Single<Result>

ネストしないで書ける! コード書く上で嬉しい点は、 hoge とか piyo がSingleが剥がれた型で取れるというところですかね。

↑について超ざっくりメモ

  • SingleK : Rxの Singleのラッパークラス
  • SingleK.monad().binding {} : this が MonadContinuation になる(途中は追ってない)
  • .k() : Single -> SingleK
  • .bind() : 中みたらコルーチンがどうので実現してるっぽくて、そのあたりちゃんとわかってないとわからなそうだなと感じた

感想

  • とりあえずツールとして使う分には便利なのかなと思った。
  • ただ、もともと実現したかった flatMapの深いネストをなくしたい に対しては巨大すぎる仕組みな感じもした。
    • そもそもarrow-ktが一番輝くのはもっと違う場面なんだろうな
    • scalaのfor式でもflatMapでネストしない書き方できるけど、そっちはただのシンタックスシュガーと聞いているし、比べると黒魔術感

参考 (公式)