2012年6月20日水曜日

Scala Tips / Validation (32) - Function0

ここまでで以下のValidationをサポートするScalazの機能を見てきました。

今回はFunction0[T]のthrowsメソッドです。

throwsメソッド

Scalazは「Function0[T]」(「() => T」)つまり引数なしの関数にthrowsメソッドを追加します。throwsメソッドは、関数の実行結果をValidation[Throwable, T]として返します。関数を実行した結果、例外が発生した場合はThrowableをFailure包んでValidation化します。このため例外のキャッチ処理を書かなくてよくなります。

throwsの動き

例としてStringのparseIntメソッドに相当する関数を自前で書くことを考えてみましょう。

parseIntメソッドの動きは以下になります。

scala> "100".parseInt
res15: scalaz.Validation[NumberFormatException,Int] = Success(100)

scala> "a".parseInt
res16: scalaz.Validation[NumberFormatException,Int] = Failure(java.lang.NumberFormatException: For input string: "a")

StringのtoIntメソッドを使うと以下になります。文字列の値がおかしい場合に例外が発生します。

scala> "100".toInt
res17: Int = 100

scala> "a".toInt
java.lang.NumberFormatException: For input string: "a"
 at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
 at java.lang.Integer.parseInt(Integer.java:449)
 at java.lang.Integer.parseInt(Integer.java:499)
(以下略)

Function0のthrowsメソッドを使うと以下になります。例外のハンドリングは自動的に行なってくれるので随分楽ですね。

scala> (() => "100".toInt).throws
res19: scalaz.Validation[Throwable,Int] = Success(100)

scala> (() => "a".toInt).throws
res20: scalaz.Validation[Throwable,Int] = Failure(java.lang.NumberFormatException: For input string: "a")

本ブログで標準にしているValidationNEL[Throwable, T]にするためには、補正にliftFailNelメソッドを用います。

scala> (() => "100".toInt).throws.liftFailNel
res22: scalaz.Validation[scalaz.NonEmptyList[Throwable],Int] = Success(100)

scala> (() => "a".toInt).throws.liftFailNel
res23: scalaz.Validation[scalaz.NonEmptyList[Throwable],Int] = Failure(NonEmptyList(java.lang.NumberFormatException: For input string: "a"))

使い方

Validation (6) - Exception」で使った課題をthrowsを使って書いてみます。Validationの文脈で例外を扱います。

課題

ValidationNEL[Throwable, String]からValidationNEL[Throwable, Int]への変換を例に考えます。Validationに入っているStringがIntに変換できる場合は有効となり、Success[NonEmptyList[Throwable], Int]が処理結果となります。一方、変換できない場合は無効となりFailure[NonEmptyList[Throwable], Int]が処理結果となります。

ここで、StringからIntへの変換ができない事の判定に例外を使用します。(String#toIntメソッドがNumberFormatExceptionをスロー。)

以前の解

Validation (6) - Exception」では、scala.util.control.ExceptionのallCatchを使うのが最終型でした。実装は以下になります。

import scala.util.control.Exception._  
  
def f(a: ValidationNEL[Throwable, String]): ValidationNEL[Throwable, Int] = {  
  a flatMap { b =>
    allCatch either {  
      b.toInt  
    } fold (_.failNel, _.success)  
  }  
}

scala.util.control.Exceptionは、例外をOptionやEitherに変換してくれる処理は持っているのですが、ScalazのオブジェクトであるValidationは当然スコープ外になります。このため、EitherからValidationへの変換処理を利用者側で書く必要があります。

throwsを使った解

throwsを使った解は以下になります。例外を直接Validation化してくれるのですっきりしますね。

def f(a: ValidationNEL[Throwable, String]): ValidationNEL[Throwable, Int] = {  
  a.flatMap(b => (() => b.toInt).throws.liftFailNel)
}

throwsはValidation[Throwable, T]を返すため、本ブログで標準にしているValidationNEL[Throwable, T]と若干型がずれます。この補正にliftFailNelメソッドを用いています。

ノート

Validation (6) - Exception」を書いた時点では、scala.util.control.ExceptionのallCatchを使うのがベストだと思っていたのですが、Function0のthrowsを使うとより簡潔に書くことができることが分かりました。例外処理はthrowsを使うのをイディオムとして整備したいと思います。

例外を細かくキャッチし分けたい場合は、引き続きscala.util.control.Exceptionのcatchingが有効です。

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

0 件のコメント:

コメントを投稿