terazzoの日記 RSSフィード

2011-10-22

[]ScalazのValidationの謎 23:51 ScalazのValidationの謎を含むブックマーク

import scalaz.Validation.Monad._するとエラーがaccumulateされないのはなぜか。

まだちょっと良く分かってないので自分の理解を書く。

ちなみにScalaz 6.0.3の話です。


Monadは普通Applicativeになる

このへんの話から。

また、Monad[M[_] ]はApplicative[M]をextendsしている。


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されること自体がおかしいような気がする。

*1:|+|は元のコードでは「⊹」です

*2:型的には({type λ[α]=Validation[X, α]})#λ