ScalazのValidationの謎
import scalaz.Validation.Monad._するとエラーがaccumulateされないのはなぜか。
まだちょっと良く分かってないので自分の理解を書く。
ちなみにScalaz 6.0.3の話です。
MonadはApplicativeに加えてBindも実装したもの
各型用のBindは大体scalaz.Bindでimplicit defされている。
MAに定義されている>>=などはMonadではなく直接Bindを見つけて来るようになっている。
def >>=[B](f: A => M[B])(implicit b: Bind[M]): M[B] = b.bind(value, f)
例えばState用のMonadだと、scalaz.Bindで
implicit def StateBind[S]: Bind[({type λ[α]=State[S, α]})#λ] = new Bind[({type λ[α]=State[S, α]})#λ] { def bind[A, B](r: State[S, A], f: A => State[S, B]) = r flatMap f }
と定義されていて、それによってStateに対して>>=などを呼び出すと、(MA, Bind経由で)flatMapが適用される。
Validation用のBind単体の定義はない
かつてはscalaz.Bindにあったけど、削除されてしまったらしい
なので1.success[String] >>= ((n:Int) => ...のように書いてもコンパイルエラーになる。
Validation用のMonad定義を使用するにはsclaz.Validation.Monad._をimportする。
Validation.scalaに定義されているが、import scalaz.*; import Scalaz.*
だけでは有効にならないようになっている。
import sclaz.Validation.Monad._
すると、Validation用のMonad定義が有効になる。
MonadはBindを継承しており、bindも定義されているので、import scalaz.Validation.Monad._
すれば>>=なども使えるようになる。
def bind[A, B](fa: Validation[X, A], f: (A => Validation[X, B])) = fa flatMap f
import scalaz.Validation.Monad._すると、エラーがaccumulateされなくなる
ソースのコメントに書いてあるとおり、import scalaz.Validation.Monad._
すると、エラーがaccumulateされなくなる
そもそもまずエラーをaccumulateしているロジックはどこにあるかというと、scalaz.ApplyにあるValidationApplyにある。*1
implicit def ValidationApply[X: Semigroup]: Apply[({type λ[α]=Validation[X, α]})#λ] = new Apply[({type λ[α]=Validation[X, α]})#λ] { def apply[A, B](f: Validation[X, A => B], a: Validation[X, A]) = (f, a) match { case (Success(f), Success(a)) => success(f(a)) case (Success(_), Failure(e)) => failure(e) case (Failure(e), Success(_)) => failure(e) case (Failure(e1), Failure(e2)) => failure(e1 |+| e2) } }
これに対して、import scalaz.Validation.Monad._
すると、applyの挙動がscalaz.Monadに実装されているものに変更される。
override def apply[A, B](f: M[A => B], a: M[A]): M[B] = { lazy val fv = f lazy val av = a bind(fv, (k: A => B) => fmap(av, k(_: A))) }
bind(fa, f)
は上に定義されているようにfa flatMap f
と等しいので、展開すると
override def apply[A, B](f: M[A => B], a: M[A]): M[B] = { lazy val fv = f lazy val av = a fv flatMap (k: A => B) => fmap(av, k(_: A))) }
となる。flatMapはValidationに定義されていて、
def flatMap[EE >: E, B](f: A => Validation[EE, B]): Validation[EE, B] = this match { case Success(a) => f(a) case Failure(e) => Failure(e) }
一方fmapはscalaz.Validation.Monadに定義されていて、
override def fmap[A, B](fa: Validation[X, A], f: (A => B)) = fa map f
このmapはscalaz.Validationに定義されている。
def map[B](f: A => B): Validation[E, B] = this match { case Success(a) => Success(f(a)) case Failure(e) => Failure(e) }
MがValidation*2であることを考慮しつつ、これをすべて展開すると、
override def apply[A, B](f: M[A => B], a: M[A]): M[B] = { lazy val fv = f lazy val av = a fv match { case Success(fva) => av match { case Success(ava) => Success(fva(ava)) case Failure(ave) => Failure(ave) } case Failure(fve) => Failure(fve) } }
fvとavのmatchを合体させて整理し、scalaz.Applyで定義されているValidationApplyの形にあわせて書くと、
// Monadic版ValidationApply implicit def ValidationApply[X]: Apply[({type λ[α]=Validation[X, α]})#λ] = new Apply[({type λ[α]=Validation[X, α]})#λ] { def apply[A, B](f: Validation[X, A => B], a: Validation[X, A]) = { lazy val fv = f lazy val av = a (fv, av) match { case (Success(f), Success(a)) => success(f(a)) case (Success(_), Failure(e)) => failure(e) case (Failure(e), _) => failure(e) } } }
となり、実際のValidationApplyの実装(上述)と違ってfailureの値がaccumulateされないのが分かる。
疑問
scalaz.Monadのapplyとscalaz.ApplyのValidationApplyのapplyのロジックが異なるのはアリなのか。
と考えると、そもそもValidationのApplicativeスタイルでエラーがaccumulateされること自体がおかしいような気がする。