如何编写 return 验证的函数?

How to compose functions that return Validation?

这是我之前 question

的后续

假设我有两个验证函数,return 要么是输入有效,要么是错误消息。

type Status[A] = ValidationNel[String, A]

val isPositive: Int => Status[Int] = 
  x => if (x > 0) x.success else s"$x not positive".failureNel

val isEven: Int => Status[Int] = 
  x => if (x % 2 == 0) x.success else s"$x not even".failureNel

还假设我需要验证 case class X:

的一个实例
case class X(x1: Int, // should be positive 
             x2: Int) // should be even

更具体地说,我需要一个函数 checkX: X => Status[X]。此外,我想将 checkX 写为 isPositiveisEven.

组合
val checkX: X => Status[X] =
  ({x => isPositive(x.x1)} |@| {x => isEven(x.x2)}) ((X.apply _).lift[Status])

有道理吗?
您如何将 checkX 写成 组合 isPositiveisEven

没有意义,因为这两个函数不能真正组合。您(大概)想检查 x 是否为正数 是否 x 为偶数 - 但在既为负数又为奇数的情况下,您想收到这两个错误。但这永远不会作为这两个函数的组合发生——一旦你应用了任何一个失败的案例,你就不再有 x 传递给第二个函数。

根据我的经验,Validation 几乎从来不是正确的类型,正是出于这个原因。

如果您想要 "fail-fast" 结果为成功或第一个错误的行为,您应该使用 \/(即本例中的 type Status[A] = String \/ A)。如果你想要 "accumulate all the error messages alongside the value" 行为,你想要 Writer,即 type Status[A] = Writer[Vector[String], A]。这两种类型都允许使用例如简单的组合(因为它们有可用的 monad 实例) Kleisli:Kleisli(isPositive) >==> isEven 适用于 Status 的任何一个定义(但不适用于你的)。

有很多种写法,但我喜欢以下几种:

val checkX: X => Status[X] = x => isPositive(x.x1).tuple(isEven(x.x2)).as(x)

或者:

val checkX: X => Status[X] =
  x => isPositive(x.x1) *> isEven(x.x2) *> x.point[Status]

关键是您希望 运行 仅针对它们的 "effects" 进行两次验证,然后 return 新上下文中的原始值。正如您自己的实现所示,这是一个完全合法的应用操作。只是有一些稍微好一点的写法。