scalaz还提供了个type class叫Validation。乍看起来跟\/没什么分别。实际上这个Validation是在\/的基础上增加了Applicative功能,就是实现了ap函数。通过Applicative实例就可以同时运算多个Validation并返回多条异常信息。所以,\/与Validation核心分别就在于Validation可以返回多条异常信息。Validation也是由两种状态组成:Success和Failure,分别与\/的left和right相对应。Failure可以返回多个值。我们先来看看Validation在scalaz里的定义:scalaz/Validation.scala
sealed abstract class Validation[+E, +A] extends Product with Serializable { ... def isSuccess: Boolean = this match { case Success(_) => true case Failure(_) => false } /** Return `true` if this validation is failure. */ def isFailure: Boolean = !isSuccess ... /** Return the success value of this validation or the given default if failure. Alias for `|` */ def getOrElse[AA >: A](x: => AA): AA = this match { case Failure(_) => x case Success(a) => a } /** Return the success value of this validation or the given default if failure. Alias for `getOrElse` */ def |[AA >: A](x: => AA): AA = getOrElse(x) /** Return the success value of this validation or run the given function on the failure. */ def valueOr[AA >: A](x: E => AA): AA = this match { case Failure(a) => x(a) case Success(b) => b } /** Return this if it is a success, otherwise, return the given value. Alias for `|||` */ def orElse[EE >: E, AA >: A](x: => Validation[EE, AA]): Validation[EE, AA] = this match { case Failure(_) => x case Success(_) => this } /** Return this if it is a success, otherwise, return the given value. Alias for `orElse` */ def |||[EE >: E, AA >: A](x: => Validation[EE, AA]): Validation[EE, AA] = orElse(x) ...
与\/非常相似,也是提供了getOrElse来获取Success[A]的A值。如果需要获取Failure[B]值则与\/一样先用swap再用getOrElse:
/** Flip the failure/success values in this validation. Alias for `unary_~` */ def swap: Validation[A, E] = this match { case Failure(a) => Success(a) case Success(b) => Failure(b) } Success(3).getOrElse(0) //> res5: Int = 3 Success("Three").getOrElse("Everything OK!") //> res6: String = Three Failure("Something wrong!").swap.getOrElse("Everything OK!") //> res7: String = Something wrong! (~Failure("Something wrong!")).getOrElse("Everything OK!") //> res8: String = Something wrong!
Validation的两个状态是这样定义的:
final case class Success[A](a: A) extends Validation[Nothing, A] final case class Failure[E](e: E) extends Validation[E, Nothing]
for { a <- Success(3) b <- Success(2) } yield a + b //> res5: scalaz.Validation[Nothing,Int] = Success(5) val valid= for { a <- Success(3) c <- Failure("oh, error!"): Validation[String,Int] d <- Failure("oh, error again!"): Validation[String,Int] b <- Success(2) } yield a + b //> valid : scalaz.Validation[String,Int] = Failure(oh, error!) if (valid.isFailure) valid.swap.getOrElse("no error") //> res6: Any = oh, error!
scalaz同样为所有类型值提供了注入方法:scalaz.syntax/ValidationOps.scala
final class ValidationOps[A](self: A) { def success[X]: Validation[X, A] = Validation.success[X, A](self) def successNel[X]: ValidationNel[X, A] = success def failure[X]: Validation[A, X] = Validation.failure[A, X](self) @deprecated("use `failure` instead", "7.1") def fail[X]: Validation[A, X] = failure[X] def failureNel[X]: ValidationNel[A, X] = Validation.failureNel[A, X](self) @deprecated("use `failureNel` instead", "7.1") def failNel[X]: ValidationNel[A, X] = failureNel[X] } trait ToValidationOps { implicit def ToValidationOps[A](a: A) = new ValidationOps(a) }
for { a <- 3.success b <- 2.success } yield a + b //> res7: scalaz.Validation[Nothing,Int] = Success(5) val pv = for { a <- 3.success c <- "oh, error!".failure[String] d <- "oh, error again!".failure[String] b <- 2.success } yield a + b //> pv : scalaz.Validation[String,Int] = Failure(oh, error!) if (pv.isFailure) (~pv).getOrElse("no error") //> res8: Any = oh, error!
不过上面两条异常信息只返回了头一条,这与\/并没有什么两样,因为它们的flatMap都是一样的:
final class ValidationFlatMap[E, A] private[scalaz](val self: Validation[E, A]) { /** Bind through the success of this validation. */ def flatMap[EE >: E, B](f: A => Validation[EE, B]): Validation[EE, B] = self match { case Success(a) => f(a) case e @ Failure(_) => e } }
当前版本的scalaz已经放弃了flatMap用法:
@deprecated("""flatMap does not accumulate errors, use `scalaz.\/` or `import scalaz.Validation.FlatMap._` instead""", "7.1") @inline implicit def ValidationFlatMapDeprecated[E, A](d: Validation[E, A]): ValidationFlatMap[E, A] = new ValidationFlatMap(d) /** Import this if you wish to use `flatMap` without a deprecation * warning. */ object FlatMap { @inline implicit def ValidationFlatMapRequested[E, A](d: Validation[E, A]): ValidationFlatMap[E, A] = new ValidationFlatMap(d) }
因为Validation又是个Applicative。它实现了ap函数:
/** Apply a function in the environment of the success of this validation, accumulating errors. */ def ap[EE >: E, B](x: => Validation[EE, A => B])(implicit E: Semigroup[EE]): Validation[EE, B] = (this, x) match { case (Success(a), Success(f)) => Success(f(a)) case (e @ Failure(_), Success(_)) => e case (Success(_), e @ Failure(_)) => e case (Failure(e1), Failure(e2)) => Failure(E.append(e2, e1)) }
((3.success : Validation[String,Int]) |@| ("oh, error1! ".failure : Validation[String,Int]) |@| (2.success : Validation[String,Int]) |@| ("oh, error2 again!".failure : Validation[String,Int])){_ + _ + _ + _} //> res13: scalaz.Validation[String,Int] = Failure(oh, error1! oh, error2 again!)
((3.successNel : ValidationNel[String,Int]) |@| ("oh, error1! ".failureNel : ValidationNel[String,Int]) |@| (2.successNel : ValidationNel[String,Int]) |@| ("oh, error2 again!".failureNel : ValidationNel[String,Int])){_ + _ + _ + _} //> res14: scalaz.Validation[scalaz.NonEmptyList[String],Int] = Failure(NonEmptyList(oh, error1! , oh, error2 again!))
/** A singly-linked list that is guaranteed to be non-empty. */ final class NonEmptyList[+A] private[scalaz](val head: A, val tail: List[A]) { ...
final class NelOps[A](self: A) { final def wrapNel: NonEmptyList[A] = NonEmptyList(self) } trait ToNelOps { implicit def ToNelOps[A](a: A) = new NelOps(a) }
val nel = 2 <:: 4 <:: 3.wrapNel //> nel : scalaz.NonEmptyList[Int] = NonEmptyList(2, 4, 3) val snel = "one" <:: "two" <:: "three".wrapNel //> snel : scalaz.NonEmptyList[String] = NonEmptyList(one, two, three) nel.list //> res17: List[Int] = List(2, 4, 3) snel.list //> res18: List[String] = List(one, two, three)