撰写Scalaz验证

chr*_*san 14 scala scalaz

我想使用Scalaz进行验证,并希望能够在不同的上下文中重用验证函数.我对Scalaz btw完全不熟悉.

假设我有这些简单的检查:

def checkDefined(xs: Option[String]): Validation[String, String] =
  xs.map(_.success).getOrElse("empty".fail)

def nonEmpty(str: String): Validation[String, String] =
  if (str.nonEmpty) str.success else "empty".fail

def int(str: String): Validation[String, Int] = ...
Run Code Online (Sandbox Code Playgroud)

我希望能够组合验证,其中一个的输出被馈送到另一个.我可以很容易地用flatMap或通过理解,但感觉必须有一个更好的方式.

for {
  v1 <- checkDefined(map.get("foo"))
  v2 <- nonEmpty(v1)
  v3 <- int(v2)
  v4 <- ...
} yield SomeCaseClass(v3, v4)
Run Code Online (Sandbox Code Playgroud)

要么

val x1 = checkDefined(map get "foo").flatMap(nonEmpty).flatMap(int)
val x2 = check(...)

// How to combine x1 and x2?
Run Code Online (Sandbox Code Playgroud)

那里的Scalaz专家有什么想法吗?

mis*_*tor 17

除了@oxbow_lakes建议的解决方案之外,您还可以使用Kleisli合成.

scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._

scala> def f: Int => Validation[String, Int] = i => if(i % 2 == 0) Success(i * 2) else    Failure("Odd!")
f: Int => scalaz.Validation[String,Int]

scala> def g: Int => Validation[String, Int] = i => if(i > 0) Success(i + 1) else Failure("Not positive!")
g: Int => scalaz.Validation[String,Int]

scala> type Va[+A] = Validation[String, A]
defined type alias Va

scala> import Validation.Monad._
import Validation.Monad._

scala> kleisli[Va, Int, Int](f) >=> kleisli[Va, Int, Int](g)
res0: scalaz.Kleisli[Va,Int,Int] = scalaz.Kleislis$$anon$1@4fae3fa6

scala> res0(11)
res1: Va[Int] = Failure(Odd!)

scala> res0(-4)
res2: Va[Int] = Failure(Not positive!)

scala> res0(4)
res3: Va[Int] = Success(9)
Run Code Online (Sandbox Code Playgroud)

类型的功能A => M[B],其中M : Monad被称为Kleisli箭头.

您可以组合两个Kleisli箭头A => M[B]并使用运算符B => M[C]获得箭头.这被称为Kleisli组合物.A => M[C]>=>

该表达式kleisli(f) >=> kleisli(g) >=> kleisli(h)相当于x => for(a <- f(x); b <- g(a); c <- h(b)) yield c减去不必要的本地绑定.

  • 哦,部分应用类型的构造函数推断! (2认同)

oxb*_*kes 13

您可能想看一下三个夜总会故事,它们使用以下方法描述验证组合:

  1. Monads(即flatMap)
  2. 应用仿函数两种方式(使用|@|traverse)

基本上规则相当于:通过monads组合是快速失败的.也就是说,您的计算将在此时短路并解决为a Failure(e).使用applicative functors意味着你可以积累失败(可能用于web表单验证) - 你可以通过使用a collection(这是a Semigroup)作为失败类型来做 - 这是可以使用的惯例NonEmptyList.

还有其他有用的东西Validation:

val1 <+> val2    //Acts like an `orElse`
val1 >>*<< val2  //Accumulates both successes and failures
Run Code Online (Sandbox Code Playgroud)

在你的具体例子中,为什么你认为"必须有更好的方法"而不是通过理解来做到这一点?但它可以稍微改进一下:

def checkDefined(xs: Option[String]) = xs.toSuccess("empty :-(")
Run Code Online (Sandbox Code Playgroud)

在这种情况下,它并不值得一个完整的方法:

for {
  v1 <- map get "foo" toSuccess "Empty :-("
  v2 <- some(v1) filterNot (_.isEmpty) toSuccess "Empty :-("
  v3 <- (v2.parseInt.fail map (_.getMessage)).validation 
  v4 <- ...
} yield SomeCaseClass(v3, v4)
Run Code Online (Sandbox Code Playgroud)