動機
昨日のScalaMatsuriで、モナド・モナドトランスフォーマーの話が出てきて、そー言えば業務でZIO使ってるけど、なんとなく使ってるなあああと思ったので、よくある問題(自分がよく直面する問題)をZIOを使って解決してみた。
問題
以下の定義があるとき、getIds
とgetUser
メソッドを組み合わせた関数の戻り型を、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とかも使ってみよう。