「Validation (34) - Semigroup」ではValidationをSemigroup化しました。その結果、演算子|+|が使用できるようになりました。
しかしSemigroup化しただけではMonoidの機能を十分に活用することはできません。たとえばsumrメソッドはMonoidが対象なので、以下のようにエラーになってしまいます。この他foldMapメソッドなどSemigroupではなくMonoidを対象にしたメソッドは多数存在しているので、できればSemigroupではなくMonoid化しておくのが得策です。
scala> List(1.success[NT], 2.success[NT], 3.success[NT]).sumr <console>:19: error: could not find implicit value for parameter m: scalaz.Monoid[scalaz.Validation[ValidationNELThrowables.NT,Int]] List(1.success[NT], 2.success[NT], 3.success[NT]).sumr ^
ScalazのMonoidはZeroとSemigroupの両型クラスを継承しています。前回はValidationの型クラスSemigroupインスタンスを作成してValidationを型クラス化しました。これに加えて型クラスZeroインスタンスを作成すれば、ValidationのZero化が行われ、同時にMonoid化も達成されます。
Validationの型クラスZeroインスタンスを追加したコードが以下になります。暗黙値VNTZeroとしてValidation[NonEmptyList[Throwable], A]に対する型クラスZeroインスタンスを定義しています。
trait ValidationNELThrowables { type NT = NonEmptyList[Throwable] type VNT[A] = Validation[NT, A] implicit def VNTZero[A: Zero]: Zero[VNT[A]] = zero(mzero[A].success) implicit def VNTSemigroup[A: Semigroup]: Semigroup[VNT[A]] = semigroup((a, b) => { (a, b) match { case (Success(va), Success(vb)) => Success(va |+| vb) case (Success(va), Failure(_)) => Success(va) case (Failure(_), Success(vb)) => Success(vb) case (Failure(ea), Failure(eb)) => Failure(ea |+| eb) }}) } object ValidationNELThrowables extends ValidationNELThrowables
VNTZeroでは、Validation[NonEmptyList[Throwable], A]がZeroであるためには、格納するオブジェクトもZeroであることを条件にしています。そしてValidation[NonEmptyList[Throwable], A]の零元は、格納するオブジェクトの零元を作成し、これをSuccessに格納にしたものになります。
動かしてみる
「Validation (34) - Semigroup」でValidationのSemigroup化、そして今回ValidationのZero化を行いました。SemigroupかつZeroのオブジェクトは自動的にMonoidとなります。つまりValidationをMonoidとして使用できるようになったわけです。その結果Monoidを対象としたさまざまなメソッドでValidationを使用できるようになりました。
sumrメソッドを使った結果は以下になります。
scala> List(1.success[NT], 2.success[NT], 3.success[NT]).sumr res25: scalaz.Validation[ValidationNELThrowables.NT,Int] = Success(6)
foldMapメソッドも普通に使えるようになりました。
scala> List(1, 2, 3).foldMap(_.success[NT]) res39: scalaz.Validation[NT,Int] = Success(6)
ノート
Optionの型クラスZeroインスタンスの仕様では零元としてNoneが返されます。
scala> mzero[Option[Int]] res2: Option[Int] = None
Optionの型クラスSemigroupインスタンスの仕様では、NoneとSomeをモノイド演算(演算子|+|)するとSomeとなります。このためNoneは零元(単位元)として機能しています。
scala> 1.some |+| 2.some res9: Option[Int] = Some(3) scala> 1.some |+| none res10: Option[Int] = Some(1) scala> none |+| 2.some res11: Option[Int] = Some(2) scala> 1.some |+| 2.some res12: Option[Int] = Some(3)
Validationの実装でも、この選択を取ってもよいのですが:
Failure(NonEmptyList(java.lang.Throwable: zero))
が単位元というのも少し違和感があるので、今回の実装では:
scala> mzero[VNT[Int]] res17: ValidationNELThrowables.VNT[Int] = Success(0)
となるようにしました。
Scalazの選択が前者の方式になっているので、数学的にはその方が筋がよい可能性がありますが、そのあたりの事情はよくわかりません。情報をいただけると助かります。
>>*<<メソッド
Validationが格納するオブジェクトがMonoidで、Validationのコンテナの結合戦略がOR演算である場合、Validationの>>*<<メソッドを使うことができます。(「Validation (26) - fold monoid or」)
このためSemigroupの実装は>>*<<メソッドを使うとより簡潔になります。>>*<<メソッドを使って改良したValidationNELThrowablesは以下になります。
trait ValidationNELThrowables { type NT = NonEmptyList[Throwable] type VNT[A] = Validation[NT, A] implicit def VNTZero[A: Zero]: Zero[VNT[A]] = zero(mzero[A].success) implicit def VNTSemigroup[A: Semigroup]: Semigroup[VNT[A]] = semigroup((a, b) => a >>*<< b) } object ValidationNELThrowables extends ValidationNELThrowables
諸元
- Scala 2.9.2
- Scalaz 6.0.4
0 件のコメント:
コメントを投稿