1
1

More than 5 years have passed since last update.

複数のモナドを同時に扱うためのネストfor式

Last updated at Posted at 2018-09-07

前置き

さて、前置きですが、モナドと書きましたが実際にはモナドではなく、モナドのようなものです。
ここで言うモナドというものは、Scalaにおけるfor式で扱えるように、以下のメソッドを実装している型のことを指します。

  • map/flatMap

(つまり、for式で使える型、ifフィルターとyieldではないfor式を除く)
あと、記事中の英語は適当です。

はじめに

どこかで見た便利な書き方としてfor式のネストした形式を紹介します。(具体的な呼び名は知らない。勝手にネストfor式とか言っておく。)
Scalaでのエラーハンドリングはキモくなりがちです。
EitherTryOptionの入り混ざったものを一つのfor式で扱うのは困難で、

最初にfor式中で使用したモナドに、以降も制限されてしまいます。

scala> {
     |     for {
     |         result <- Future { Try("hanage") }
     |         hanage <- result
     |     } yield hanage
     | }
<console>:22: error: type mismatch;
 found   : scala.util.Try[String]
 required: scala.concurrent.Future[?]
               hanage <- result
                      ^

さて、この全部の失敗した結果をまとめてハンドリングしたいが、Scala標準ライブラリだけでやりたいです。
(関数型ライブラリなどをプロダクト開発に使おうとすると、扱える人が極端に限定されるので、個人的にはなるべく扱いたくない。)

ネストfor式を使おう

標準のfor式の構文は以下のようになっており、seqはジェネレーター、定義、フィルターを連続した形で指定する。連続した要素はセミコロンで区切られます。

for ( seq ) yield expr

このexprの部分にfor式をネストした形で記述するのがfor式のネストですね。(そのまま)

for ( seq ) yield {
    for ( seq ) yield {
        ...
    }
}

この各for式は、当然のように異なるfor式なので、for式一つにつきモナドを使用することができますね。
つまり、複数のモナドを一つ(に見せかけた)のfor式で扱いたい場合に使用することができます。

以下に実際にFutureTryをまとめてハンドリングしたネストfor式の例を、、

scala> {
     |     for {
     |         result <- Future { throw new Exception; Failure(new Exception) }
     |     } yield for {
     |         string <- result
     |     } yield string
     | } onComplete {
     |     case Success(Failure(_)) => println("Future is Success, but hanage is Failure")
     |     case Success(_) => println("Success All")
     |     case Failure(e) => println(s"Future is Failure, because of $e")
     | }
Future is Failure, because of java.lang.Exception
scala> {
     |     for {
     |         result <- Future { Failure(new Exception) }
     |     } yield for {
     |         string <- result
     |     } yield string
     | } onComplete {
     |     case Success(Failure(_)) => println("Future is Success, but hanage is Failure")
     |     case Success(_) => println("Success All")
     |     case Failure(e) => println(s"Future is Failure, because of $e")
     | }

scala> Future is Success, but hanage is Failure
scala> {
     |     for {
     |         result <- Future { Success(new Exception) }
     |     } yield for {
     |         string <- result
     |     } yield string
     | } onComplete {
     |     case Success(Failure(_)) => println("Future is Success, but hanage is Failure")
     |     case Success(_) => println("Success All")
     |     case Failure(e) => println(s"Future is Failure, because of $e")
     | }
Success All

Futureによって、ネストされたfor式は別スレッドで実行されます。
全ての処理が完了した時の処理をonCompleteに記述します。

onCompleteに渡すのはPartialFunctionなので、エラーハンドリングは別の関数として定義しておいて受け渡すと例外処理ロジックのノイズが軽減されます。

val errorHandler: Try[Try[String]] => Unit = {
    case Success(Failure(_)) => println("Future is Success, but hanage is Failure")
    case Success(_) => println("Success All")
    case Failure(e) => println(s"Future is Failure, because of $e")
}

{
    for {
        result <- Future { throw new Exception; Failure(new Exception) }
    } yield for {
        string <- result
    } yield string
} onComplete errorHandler

当然FutureとTryだけでなく、そのほかの全てのモナドで同様のことが可能です。
ただ、モナドの種類だけ結果の値がネストしてしまうので(同一のモナドの結果はflatMapによって潰される)、
なるべくモナドの種類は統一するように心がけよう。

1
1
2

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