2012年4月24日火曜日

Scala Tips / Validation (7) - for

ValidationはScalazが提供する成功/失敗の計算文脈を提供するモナドです。Validationを使ってOptionやEitherと同様の成功/失敗の計算文脈上でのMonadicプログラミングをすることができます。

今回は「Option (7)」や「Either (7) - for」で扱った課題のValidation版を考えてみます。

Validation (4) - map」で成功/失敗の文脈を切り替えない以下の演算をmapで行う方法:

条件結果
Validation[A, B]がSuccess[A, B]Success[A, C]
Validation[A, B]がFailure[A, B]Failure[A, C]

Validation (5) - flatMap」で成功/失敗の文脈を切り替える以下の演算をflatMapで行う方法について説明しました。

条件結果
Validation[A, B]がSuccess[A, B]でBに有効な値が入っているSuccess[A, C]
Validation[A, B]がSuccess[A, B]でBに無効な値が入っているFailure[A, C]
Validation[A, B]がFailure[A, B]Failure[A, C]

大枠ではB→Cの演算を行いたいわけですが、これをfor式を用いてValidation[A, B→C]の文脈の上で行うわけです。

今回はこの2つの演算をfor式を使って書いてみます。

以下ではValidation[NonEmptyList[Throwable], Int]をValidation[NonEmptyList[Throwable], String]に変換するプログラムを考えます。なお、Validation[NonEmptyList[Throwable], Int]はValidationNEL[Throwable, Int]と同等なので、可能な場合はValidationNELの方の表記を用います。

文脈切替なし

Validation (4) - map」で取り上げた成功の文脈と失敗の文脈の切り替えが発生しない演算です。以下の表に示す演算になります。

条件結果
Validation[A, B]がSuccess[A, B]Success[A, C]
Validation[A, B]がFailure[A, B]Failure[A, C]

ValidationNEL[Throwable, Int]からValidation[Throwable, String]への変換は以下のようになります。mapメソッドと同等の処理をfor式で素直に記述できます。

def f(a: ValidationNEL[Throwable, Int]): ValidationNEL[Throwable, String] = {
  for (b <- a) yield {
    b.toString
  }
}

文脈切替あり

Validation (5) - flatMap」で取り上げた成功の文脈と失敗の文脈の切り替えが発生する演算です。以下の表に示す演算になります。

条件結果
Validation[A, B]がSuccess[A, B]でBに有効な値が入っているSuccess[A, C]
Validation[A, B]がSuccess[A, B]でBに無効な値が入っているFailure[A, C]
Validation[A, B]がFailure[A, B]Failure[A, C]

Intは0以上のものが有効という条件付きのValidationNEL[Throwable, Int]からValidation[Throwable, String]への変換は以下のようになります。

def f(a: ValidationNEL[Throwable, Int]): ValidationNEL[Throwable, String] = {
  def g(c: ValidationNEL[Throwable, Int]) = {
    c.flatMap { d =>
      if (d >= 0) c
      else new IllegalArgumentException(d.toString).failNel
    }
  }

  for (b <- g(a)) yield {
    b.toString
  }
}

Optionの場合と違ってValidationではif句で成功文脈から失敗文脈への切り替え条件を指定することはできません。ValidationはwithFilterメソッド、filterメソッドを持っていないためです。

そこで、ジェネレータに指定する前に成功文脈から失敗文脈への切り替え判定をしておくようにプログラミングしてみました。この処理をfor式内に直接記述するとプログラムの見通しが悪くなるので、ローカル関数を作成し、これを呼び出すようにしています。

プログラムの見通しはかなり悪くなってしまっていて、ちょっと無理がある感じですね。このためValidationを計算文脈として成功文脈から失敗文脈への切り替えを持つ処理をfor式で書くのはあまり得策ではないようです。

ノート

for式はモナドの文法糖衣で、うまくはまると便利なのですが、成功文脈から失敗文脈への切り替えはうまくハンドリングできない感じです。

今回のような簡単な例だとflatMapメソッドを直接使ったほうが簡潔で便利です。

ただし、計算文脈上で多段の計算を行う場合はfor式が便利なのは確かで、「成功文脈から失敗文脈への切り替え」の不細工さを勘案してもfor式を選ぶ方が良いケースもありそうです。このあたりの取捨選択は追々考えていく予定です。

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

0 件のコメント:

コメントを投稿