4
1

More than 3 years have passed since last update.

[Scala] Future[Option[T]]をZIOで合成

Last updated at Posted at 2020-10-18

動機

昨日のScalaMatsuriで、モナド・モナドトランスフォーマーの話が出てきて、そー言えば業務でZIO使ってるけど、なんとなく使ってるなあああと思ったので、よくある問題(自分がよく直面する問題)をZIOを使って解決してみた。

問題

以下の定義があるとき、getIdsgetUserメソッドを組み合わせた関数の戻り型を、Future[Option[User]]として返したい。
scalazでいう、OptionT的なことをしたい。(scalaz使ったことないけど。。。)

case class User(name: String)
implicit val ec = ExecutionContext.global

def getIds: Future[Option[String]] = Future(Some("uuid"))
def getUser(id: String): Future[Option[User]] = Future(Some(User("jon")))

力技でやってみる

flatMap, mapでいけるか?

val result1: Future[Option[Future[Option[User]]]] = getIds.map(maybeId => maybeId.map(id => getUser(id)))

上の糖衣構文

val result2 = for {
  maybeId <- getIds
} yield {
  for {
    id <- maybeId
  } yield {
    getUser(id)
  }
}

流石にflatMapとmapだけじゃ無理や。。。
下のようにパターンマッチングを利用するしか。


val result3: Future[Option[User]] = getIds.flatMap {
  case None => Future(None)
  case Some(id) =>
    getUser(id).map {
      case None       => None
      case Some(user) => Some(user)
    }
}

一応できたけど、連結したいメソッドが増えれば増えるだけネストが深くなってしまう。
さらに、Future(None)みたいな書き方もなんかなあ。

ZIOでやってみる

そこで、ZIOのmonad transformerを使って、Future[Option[T]]を合成していく。

val result4: ZIO[Any, Throwable, Option[User]] = {
  ZIO.fromFuture{implicit ec => getIds}.flatMap((maybeId: Option[String]) =>
    ZIO.fromOption(maybeId).mapError(_ => new Exception).flatMap(id =>
      ZIO.fromFuture{implicit ec => getUser(id)}
    ))
}

fromFuture, fromOptionを使うことで、Future, Optionをいい感じに合成できるようだ。
ちょっと読みにくいので、for式で書いてみる。

val result5: ZIO[Any, Throwable, Option[User]] = for {
  maybeId <- ZIO.fromFuture{ implicit ec => getIds}
  id <- ZIO.fromOption(maybeId).mapError(_ => new Exception)
  user <- ZIO.fromFuture{implicit ec => getUser(id)}
} yield {
  user
}

お、だいぶ綺麗になった。
さらに以下のように書ける。

val result6: ZIO[Any, Throwable, Option[User]] = (for {
  // ZIO.fromFuture{ implicit ec => getIds}: ZIO[Any, Throwable, Option[String]]
  // .someとすると、ZIO[R, Option[E], B]が返る
  // Option[String]がNoneの場合は、ZIO.failが返り、処理が途中で終わる
  // someメソッドの中身に詳細書いてる
  id <- ZIO.fromFuture{ implicit ec => getIds}.some
  user <- ZIO.fromFuture{implicit ec => getUser(id)}.some
} yield {
  user
}).optional

まとめ

scala初めて一年くらいだが、最近やっと関数型の良さを理解し始めた気がする。(遅い)
scalaz, catsとかも使ってみよう。

参考

4
1
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
4
1