ここまでで以下のValidationをサポートするScalazの機能を見てきました。
- Validation (12) - parse : Stringのparseメソッド
- Validation (13) - toValidation : Function1[T, Boolean]のtoValidationメソッドの応用
- Validation (31) - Function1 : Function1[T, Boolean]のtoValidationメソッドの基本情報
今回は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 件のコメント:
コメントを投稿