我想使用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] = ...我喜欢能够编写验证,其中一个的输出被提供给另一个。我可以很容易地使用flatMap或via进行理解,但感觉一定有比这更好的方法。
for {
v1 <- checkDefined(map.get("foo"))
v2 <- nonEmpty(v1)
v3 <- int(v2)
v4 <- ...
} yield SomeCaseClass(v3, v4)或
val x1 = checkDefined(map get "foo").flatMap(nonEmpty).flatMap(int)
val x2 = check(...)
// How to combine x1 and x2?Scalaz专家有什么想法吗?
发布于 2012-02-24 18:21:33
除了@oxbow_lakes建议的解决方案外,您还可以使用克莱斯利组合。
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)A => M[B]类型的函数,其中M : Monad称为克莱斯利箭头。
您可以使用A => M[C]运算符组合两个克雷斯利箭头A => M[B]和B => M[C],以获得箭头A => M[C]。这就是所谓的克莱斯利组合。
表达式kleisli(f) >=> kleisli(g) >=> kleisli(h)等同于x => for(a <- f(x); b <- g(a); c <- h(b)) yield c,去掉了不必要的本地绑定。
发布于 2012-02-24 18:01:32
您可能需要查看,它使用以下命令描述验证组合:
两种方式的flatMap)
traverse)和
基本上,规则是这样的:通过monad进行组合是快速失败的。也就是说,您的计算将在这一点上短路并解析为Failure(e)。使用应用函数器意味着您可以累积失败(可能用于web表单验证)-这是通过使用collection (这是一个Semigroup)作为失败类型来实现的-规范示例使用NonEmptyList。
在Validation上还有其他有用的东西:
val1 <+> val2 //Acts like an `orElse`
val1 >>*<< val2 //Accumulates both successes and failures在你的具体例子中,为什么你认为“肯定有更好的方法”,而不是通过for-comprehension?不过,它还可以稍微改进一下:
def checkDefined(xs: Option[String]) = xs.toSuccess("empty :-(")在这种情况下,它实际上不值得使用完整的方法:
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)发布于 2012-02-24 18:55:29
表达式
for {
v1 <- checkDefined(map.get("foo"))
v2 <- nonEmpty(v1)
v3 <- int(v2)
v4 <- someComputation()
} yield SomeCaseClass(v3, v4)可以用这种方式替代
(checkDefined(map.get("foo")).liftFailNel |@| nonEmpty(v1)) {(v1, v2) =
SomeCaseClass(int(v2), someComputation)
}结果将会是
Validtion[NonEmptyList[String], SomeCaseClass] which is equal to ValidationNEL[String, SomeCaseClass]如果两个验证都失败,则NonEmptyList将同时包含这两个验证
https://stackoverflow.com/questions/9428174
复制相似问题