编写Scalaz验证
我希望使用Scalaz进行验证,并希望能够在不同的上下文中重用验证函数。顺便说一句,我对Scalaz完全陌生 假设我有这些简单的支票:编写Scalaz验证,scala,scalaz,Scala,Scalaz,我希望使用Scalaz进行验证,并希望能够在不同的上下文中重用验证函数。顺便说一句,我对Scalaz完全陌生 假设我有这些简单的支票: def checkDefined(xs: Option[String]): Validation[String, String] = xs.map(_.success).getOrElse("empty".fail) def nonEmpty(str: String): Validation[String, String] = if (str.nonE
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)
Scalaz专家有什么想法吗?您可能想看看下面描述验证组合的:
flatMap
)@
和遍历
)集合
(它是半群
)作为失败类型来实现的-Canconic示例使用非空列表
关于验证
还有其他有用的内容:
val1 <+> val2 //Acts like an `orElse`
val1 >>*<< val2 //Accumulates both successes and failures
在这种情况下,它实际上不值得一个完整的方法:
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)
用于{
v1除了@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)
类型为A=>M[B]
的函数,其中M:Monad
称为Kleisli箭头
您可以使用=>
运算符组合两个Kleisli箭头A=>M[B]
和B=>M[C]
以获得一个箭头A=>M[C]
。这称为Kleisli组合
表达式kleisli(f)>=>kleisli(g)>=>kleisli(h)
相当于(a表达式的x=>
for {
v1 <- checkDefined(map.get("foo"))
v2 <- nonEmpty(v1)
v3 <- int(v2)
v4 <- someComputation()
} yield SomeCaseClass(v3, v4)
结果将是
Validtion[NonEmptyList[String], SomeCaseClass] which is equal to ValidationNEL[String, SomeCaseClass]
如果两个验证都失败,NonEmptyList将同时包含这两个验证我最近编写了一个简单的“框架”对于可组合的声明性验证,我最初是基于@missingfaktor的答案实现的,然而,在他提出的基础上,我添加了一个DSL,用于处理输入的任意大小的元组,这些元组被输入到匹配的算术函数中进行验证
其用途如下:
def非空[A]=(msg:String)=>Vali{A:Option[A]=>
a、 toSuccess(msg)
}
def validIso2CountryCode=(msg:String)=>Vali{x:String=>
IsoCountryCodes2to3.get(x).toSuccess(msg)
}
val postal=“12345”。有些
val country=“GB”。一些
val参数=(
邮政的
|>非空[字符串](“需要邮寄”),
国家
|>非空[字符串](“需要国家/地区”)
>=>validIso2CountryCode(“国家/地区必须有效”)
)
//由于实现的泛型类型级别特性,参数类型推断在这里不起作用;欢迎进行任何改进!
验证(参数){(邮政:字符串,国家:字符串)=>
println(s“邮政:$postal,国家:$country”)
}
除了missingfaktor的答案外,您可能会注意到scalaz 7没有用于验证的Monad
,因为它的行为与应用
实例不匹配。因此,您可以为验证
定义绑定
,以及转换器,以方便:
import scalaz.{Bind, Kleisli, Validation, Success, Failure}
implicit def toKleisli[E, A, B](f: A => Validation[E, B]): Kleisli[Validation[E, ?], A, B] =
Kleisli[Validation[E, ?], A, B](f)
implicit def fromKleisli[E, A, B](f: Kleisli[Validation[E, ?], A, B]): A => Validation[E, B] = f.run
implicit def validationBind[E] = new Bind[Validation[E, ?]] {
def bind[A, B](fa: Validation[E, A])(f: (A) => Validation[E, B]): Validation[E, B] = {
import Validation.FlatMap._
fa.flatMap(f)
}
def map[A, B](fa: Validation[E, A])(f: (A) => B): Validation[E, B] = fa.map(f)
}
val parse: Option[String] => Validation[String, Int] = checkDefined _ >=> nonEmpty _ >=> int _
println(parse(None)) // Failure(empty)
println(parse(Some(""))) // Failure(empty)
println(parse(Some("abc"))) // Failure(java.lang.NumberFormatException: For input string: "abc")
println(parse(Some("42"))) // Success(42)
那么“(x1 |@x2){(x1,x2)=>…}”呢虽然我不太确定确切的语法…看,实际上我现在打开了一个选项卡。这是一个很好的例子。我正在努力解决的问题是,我希望我的检查组合起来。也就是说,一个检查的输出应该是下一个检查的输入。就像你的例子中,当你在列表中有检查函数时,它们都会检查在同一个人的实例中,这与我尝试做的不同。您正在描述flatMap
;也就是说,您已经知道答案了!非常感谢!不,在这种情况下,它不需要完整的方法,但我需要在不同的上下文中验证相同的内容。例如,一个id本身和一个具有id的实体以及其他一些字段。我所说的“更好的方法”是指像你上面描述的那些看起来很酷的短方法,这对于我们来说并不明显,因为我们有一个命令式的背景,想要更具功能性等等。哈哈,我也有一个命令式的背景。我已经使用验证大约18个月了,我只是“发现”
几个月前,当我让你上线的时候……什么是最好的方法来利用现有的验证并改变失败。这就是你在上面的示例中所做的方法吗checkLong(x).map(Some()).fail.map(=>“oops”).validation
Oh对于部分应用的类型构造函数推理!@oxbow_lakes,这是Scala中最需要的东西之一。不幸的是,它似乎不在他们的短期列表中。正如@oxbow_lakes指出的,有一种短路方法和一种累积方法。这个例子就是短路方法。你怎么不知道呢如果你想累积失败的话,就可以完成他的任务?说真的。既然你能很好地回答快速失败的问题,你能读懂这个问题吗?@missingfakt或者你的例子对scalaz 7不再有效。没有验证。Monad对象了。你知道如何让kleisli-arrow合成与scalaz 7验证一起工作吗?呃,不,它不能。第一次验证的成功结果需要作为第二次验证的输入,因此应用程序函子在这里无法帮助您
(checkDefined(map.get("foo")).liftFailNel |@| nonEmpty(v1)) {(v1, v2) =
SomeCaseClass(int(v2), someComputation)
}
Validtion[NonEmptyList[String], SomeCaseClass] which is equal to ValidationNEL[String, SomeCaseClass]
import scalaz.{Bind, Kleisli, Validation, Success, Failure}
implicit def toKleisli[E, A, B](f: A => Validation[E, B]): Kleisli[Validation[E, ?], A, B] =
Kleisli[Validation[E, ?], A, B](f)
implicit def fromKleisli[E, A, B](f: Kleisli[Validation[E, ?], A, B]): A => Validation[E, B] = f.run
implicit def validationBind[E] = new Bind[Validation[E, ?]] {
def bind[A, B](fa: Validation[E, A])(f: (A) => Validation[E, B]): Validation[E, B] = {
import Validation.FlatMap._
fa.flatMap(f)
}
def map[A, B](fa: Validation[E, A])(f: (A) => B): Validation[E, B] = fa.map(f)
}
val parse: Option[String] => Validation[String, Int] = checkDefined _ >=> nonEmpty _ >=> int _
println(parse(None)) // Failure(empty)
println(parse(Some(""))) // Failure(empty)
println(parse(Some("abc"))) // Failure(java.lang.NumberFormatException: For input string: "abc")
println(parse(Some("42"))) // Success(42)