2012年6月11日月曜日

Scala Tips / Validation (27) - fold or

Validationの集りに対して畳込みを行う場合、Failureの扱いには以下の2つの選択肢があります。

  • Failureが一つでもあれば畳込み全体をFailureにする。
  • Failureは飛ばしてSuccessのみを畳込む。

前回は、「Failureは飛ばしてSuccessのみを畳込む」の処理について、Validationが格納する要素がMonoidで、畳込み演算がMonoidの加算処理という条件で実装を行いました。このケースでは、foldメソッドは使う必要はあるものの、畳込み演算そのものはValidationの「>>*<<」メソッドを使って簡潔に記述することができました。

今回は「格納する要素がMonoidで、畳み込み演算がMonoidの加算処理」という条件を外して、より汎用的な処理方法について考えます。

課題

Stringを格納したValidationのリスト上で、StringをIntに変換後のIntを加算で畳み込んだ結果の値を格納したValidationを生成します。ただし、ValidationがFailureがあった場合には、Failureは飛ばしてSuccessのものだけを畳み込み結果をSuccessにします。

具体的には、以下の関数を作ります。

  • f(a: List[ValidationNEL[Throwable, String]): ValidationNEL[Throwable, Int]

実装

課題の実装は以下になります。

def f(a: List[ValidationNEL[Throwable, String]]): ValidationNEL[Throwable, Int] = {
  type VNT[A] = ValidationNEL[Throwable, A]
  a.foldLeft(0.pure[VNT]) {
    (a, x) => x.flatMap(_.parseInt) match {
      case Success(s) => a.map(_ + s)
      case Failure(e) => a
    }
  }
}

かなりカスタムな処理になるので、foldLeftを使って地道にロジックを記述します。

初期値は「0.pure[VNT]」です。

畳込み演算のポイントは2つあります。一つはValidationの入れ子の捌き方です。Validation上のStringにparseIntメソッドを適用するとValidationが生成されるため、Validationの入れ子の中にStringが入ることになります。このValidationの入れ子を一つのValidationにまとめる必要があります。つまりValidation[String]→Validation[Validation[Int]]→Validation[Int]の変換を行うわけですが、これはString→Validation[Int]のmap演算とValidation[Validation[Int]]→Validation[Int]のjoin演算を連続実行したものです。さらにmap演算とjoin演算を合成したものがモナドのbind演算なので、このbind演算を行えばよいわけです。Scalaのbind演算はflatMapメソッドですから、flatMapメソッドでString→Validation[Int]を行うStringのparseIntメソッドを指定します。

もう一つは、flatMapメソッドの実行結果として得られるvalidation[Int]を、実際に畳込み込んでいく処理です。この処理が今回のテーマである「Failureは飛ばしてSuccessのみを畳込む」になります。ValidationのApplicative処理とは異なった処理をすることになるので、地道にmatch式で条件判定して以下の処理を記述します。

  • Successなら、積算値のValidationに値を足し込む。(mapメソッドを使用)
  • Failureなら、積算値をそのまま使う。(Failureの情報は捨てる)

入力がStringのListの場合

参考に、入力がStringを格納したValidationのListではなく、StringのListの場合を考えます。この場合は、前述のようにflatMapは使う必要はなく、parseIntメソッドの結果を直接match式で条件判定してケースごとの処理を記述します。

def f(a: List[String]): ValidationNEL[Throwable, Int] = {
  type VNT[A] = ValidationNEL[Throwable, A]
  a.foldLeft(0.pure[VNT]) {
    (a, x) => x.parseInt match {
      case Success(s) => a.map(_ + s)
      case Failure(e) => a
    }
  }
}

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

0 件のコメント:

コメントを投稿