2012年6月19日火曜日

Scala Tips / Validation (31) - Function1

エラーが発生する処理を書く場合、Option、Either、Validationのいずれかを使って「成功または失敗の文脈」の中でエラー情報を引き継いでいくことになります。この中でValidationはScalazのオブジェクト/モナドということもあって、Scalazの機能の中で色々なサポートが行われています。

ここまでで以下の2つを紹介しました。

今回はtoValidationメソッドの紹介のし直しです。

Validation (13) - toValidation」は、「Validation (14) - オブジェクトの生成」による応用のための部品を準備するという趣旨の記事だったため、Function1[T, Boolean]のtoValidationメソッドの使い方のカタログという観点にはなっていませんでした。今回は「Validation (13) - toValidation」の記事の前段階ぐらいの内容にあたる基本的な使い方を整理します。

toValidationメソッド

「Function1[T, Boolean]」(「T => Boolean」)がある場合に、これからValidationを生成する関数を作り出すのがtoValidationメソッドです。たとえば以下のisValidAge関数はInt値を判定して真偽をBooleanで返す関数ですが、こういった関数から真偽をValidationで返す関数を作ります。

def isValidAge(age: Int): Boolean = {
  0 <= age && age <= 150
}

これを関数オブジェクト化するとtoValidationメソッドが使えるようになります。

scala> val b = isValidAge _
b: Int => Boolean = <function1>

関数オブジェクトのtoValidationメソッドで新しい関数オブジェクトを生成します。toValidationメソッドでは、真偽の判定が偽になった場合に生成するFailureの値を指定します。Failureには自動的にNonEmptyListが使用されるようになっています。

scala> val v = b.toValidation(new IllegalArgumentException("bad age"))
v: Int => scalaz.Validation[scalaz.NonEmptyList[java.lang.IllegalArgumentException],Int] = <function1>

toValidationメソッドで生成したValidation化された判定関数の動作は以下となります。

scala> v(10)
res0: scalaz.Validation[scalaz.NonEmptyList[java.lang.IllegalArgumentException],Int] = Success(10)

scala> v(-1)
res1: scalaz.Validation[scalaz.NonEmptyList[java.lang.IllegalArgumentException],Int] = Failure(NonEmptyList(java.lang.IllegalArgumentException: bad age))

scala> v(200)
res2: scalaz.Validation[scalaz.NonEmptyList[java.lang.IllegalArgumentException],Int] = Failure(NonEmptyList(java.lang.IllegalArgumentException: bad age))

使い勝手

toValidationメソッドの使い勝手について考えてみます。

toValidationメソッドを使わないで普通に書くと以下のようになります。

def validateAge(age: Int): ValidationNEL[Throwable, Int] = {
  if (isValidAge(age)) age.success
  else new IllegalArgumentException("bad age = " + age).failNel
}

toValidationメソッドを使うと、以下のように新しい関数オブジェクトを変数に設定することで、新しい関数を定義するようなことが簡単にできます。

val validateAge = (isValidAge _).toValidation(new IllegalArgumentException("bad age"))

ただし、一つ問題があってエラー情報の中にオリジナルの値の情報を入れることができません。「new IllegalArgumentException("bad age")」はできても、「new IllegalArgumentException("bad age = " + age)」はできないということですね。

これをやるためには以下のようにメソッド化します。toValidationを使わない場合とコーディング量はあまり変わらなくなってしまいます。

def validateAge(age: Int): ValidationNEL[Throwable, Int] = {
  val f = (isValidAge _).toValidation(new IllegalArgumentException("bad age = " + age))
  f(age)
}

ということで、toValidationメソッドは万能というわけではないですが、Function1[T, Boolean]からFunction1[T, ValidationNEL[Throwable, T]]という部品を作りたいケースは割とよくあるので、条件が合えば便利に利用できそうです。

続き

この記事の後、応用編として「Validation (13) - toValidation」が続きます。

諸元

  • Scala 2.9.2
  • Scalaz 6.0.4

0 件のコメント:

コメントを投稿