Scalaz(20)-Monad: Validation-Applicative版本的Either

  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]

Validation也是一个Monad,可以在for-comprehension中实现Failure立即退出功能:

 1 for {
 2   a <- Success(3)
 3   b <- Success(2)
 4 } yield a + b                                     //> res5: scalaz.Validation[Nothing,Int] = Success(5)
 5 
 6 val valid= for {
 7   a <- Success(3)
 8   c <- Failure("oh, error!"): Validation[String,Int]
 9   d <- Failure("oh, error again!"): Validation[String,Int]
10   b <- Success(2)
11 } yield a + b                                     //> valid  : scalaz.Validation[String,Int] = Failure(oh, error!)
12 if (valid.isFailure) valid.swap.getOrElse("no error")
13                                                   //> 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)
}

上面的例子也可以这样写:

 1 for {
 2   a <- 3.success
 3   b <- 2.success
 4 } yield a + b                                     //> res7: scalaz.Validation[Nothing,Int] = Success(5)
 5 
 6 val pv = for {
 7   a <-  3.success
 8   c <- "oh, error!".failure[String]
 9   d <- "oh, error again!".failure[String]
10   b <-  2.success
11 } yield a + b                                     //> pv  : scalaz.Validation[String,Int] = Failure(oh, error!)
12 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))
  }

我们可以同时运算几个Validation算法并返回所有异常信息:

((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!)

我们看到即使其中两项运算出现异常但还是完成了所有运算并且返回了两条异常信息。不过这两条信息合并在了String里,可能不方便后续处理。Validation注入方法提供了failureNel函数。我们试着用用:

((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!))

现在这两条信息被放进了NonEmptyList里。NonEmptyList就是一种List,不过没有Nil状态。看看它的定义:scalaz/NonEmptyList.scala

/** A singly-linked list that is guaranteed to be non-empty. */
final class NonEmptyList[+A] private[scalaz](val head: A, val tail: List[A]) {
...

至少这个List含有head元素。NonEmptyList的构建器在注入方法中:scalaz/NonEmptyListOps.scala

final class NelOps[A](self: A) {
  final def wrapNel: NonEmptyList[A] =
    NonEmptyList(self)
}

trait ToNelOps {
  implicit def ToNelOps[A](a: A) = new NelOps(a)
}

我们简单地试用这个NonEmptyList:

1  val nel = 2 <:: 4 <:: 3.wrapNel                  //> nel  : scalaz.NonEmptyList[Int] = NonEmptyList(2, 4, 3)
2  val snel = "one" <:: "two" <:: "three".wrapNel   //> snel  : scalaz.NonEmptyList[String] = NonEmptyList(one, two, three)
3  nel.list                                         //> res17: List[Int] = List(2, 4, 3)
4  snel.list                                        //> res18: List[String] = List(one, two, three)

我们可以直接把它转成List再进行处理操作。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏博岩Java大讲堂

Java集合--阻塞队列(BlockingQueue)

3287
来自专栏java一日一条

Java 动态代理深入解析

要想了解Java动态代理,首先要了解什么叫做代理,熟悉设计模式的朋友一定知道在Gof总结的23种设计模式中,有一种叫做代理(Proxy)的对象结构型模式,动态代...

775
来自专栏积累沉淀

反射封装request参数为对象

public class RequestUtil { public static<T> T parseRequset( HttpServletReq...

19510
来自专栏Java爬坑系列

【Java入门提高篇】Day26 Java容器类详解(八)HashSet源码分析

  前面花了好几篇的篇幅把HashMap里里外外说了个遍,大家可能对于源码分析篇已经讳莫如深了。

724
来自专栏老马说编程

(41) 剖析HashSet / 计算机程序的思维逻辑

查看历史文章,请点击上方链接关注公众号。 上节介绍了HashMap,提到了Set接口,Map接口的两个方法keySet和entrySet返回的都是Set,本节,...

1799
来自专栏10km的专栏

fastjson:javabean按字段(field)序列化存储为Map并反序列化

大部分json工具对java对象整体序列化都提供了简单的调用方式,以fastjson为例: Model model = new Model(); String ...

2625
来自专栏Java技术栈

Java List面试题汇总

1、你知道的List都有哪些? 2、List和Vector有什么区别? 3、List是有序的吗? 4、ArrayList和LinkedList的区别?分别用在什...

3459
来自专栏菩提树下的杨过

mybatis3.2.8 与 hibernate4.3.6 混用

mybatis、hibernate这二个框架各有特色,对于复杂的查询,利用mybatis直接手写sql控制起来更灵活,而一般的insert/update,hib...

1796
来自专栏代码拾遗

反射基础之Class

Java中每个类型要么是引用类型,要么是原生类型。类,枚举,数组(他们都继承于java.lang.Object)和接口都是引用类型。例如:java.lang.S...

814
来自专栏Java Edge

"聊胜于无",浅析Java中的原子操作Java的指针Unsafe类i++不是线程安全的1 原子更新基本类型类2 原子更新数组3 AtomicReference(原子更新引用)4 原子更新字段Atomi

4436

扫码关注云+社区