我想确认婚姻无效方法的参数,但我没有找到解决方案...
谁能告诉我该怎么办?
我想是这样的:
def buildNormalCategory(user: User, parent: Category, name: String, description: String): Either[Error,Category] = {
val errors: Option[String] = for {
_ <- Option(user).toRight("User is mandatory for a normal category").right
_ <- Option(parent).toRight("Parent category is mandatory for a normal category").right
_ <- Option(name).toRight("Name is mandatory for a normal category").right
errors : Option[String] <- Option(description).toRight("Description is mandatory for a normal category").left.toOption
} yield errors
errors match {
case Some(errorString) => Left( Error(Error.FORBIDDEN,errorString) )
case None => Right( buildTrashCategory(user) )
}
}
如果您愿意使用Scalaz ,它有工具,使这样的任务更方便,包括一个新的屈指可数Validation
类和普通的老一些有用的右侧偏置型类实例scala.Either
。 我在这里给每个的例子。
积累与错误Validation
首先我们Scalaz进口(请注意,我们必须隐藏scalaz.Category
避免名称冲突):
import scalaz.{ Category => _, _ }
import syntax.apply._, syntax.std.option._, syntax.validation._
我使用Scalaz 7的这个例子。 你需要做一些小的改动,以使用6。
我会认为我们有这个简化的模型:
case class User(name: String)
case class Category(user: User, parent: Category, name: String, desc: String)
接下来,我将定义下面的验证方法,而如果你移动到不涉及检查空值的方法,你可以很容易地适应:
def nonNull[A](a: A, msg: String): ValidationNel[String, A] =
Option(a).toSuccess(msg).toValidationNel
的Nel
部分代表“非空列表”,以及ValidationNel[String, A]
是基本上相同的作为Either[List[String], A]
现在我们用这个方法来检查我们的论据:
def buildCategory(user: User, parent: Category, name: String, desc: String) = (
nonNull(user, "User is mandatory for a normal category") |@|
nonNull(parent, "Parent category is mandatory for a normal category") |@|
nonNull(name, "Name is mandatory for a normal category") |@|
nonNull(desc, "Description is mandatory for a normal category")
)(Category.apply)
需要注意的是Validation[Whatever, _]
是不是一个单子(为讨论的原因在这里 ,例如),但ValidationNel[String, _]
是一个适用函子,而我们使用的事实,在这里,当我们的“升降机” Category.apply
到它。 请参阅下面的附录上的应用性函子的更多信息。
现在,如果我们写的是这样的:
val result: ValidationNel[String, Category] =
buildCategory(User("mary"), null, null, "Some category.")
,我们将获得与积累错误失败:
Failure(
NonEmptyList(
Parent category is mandatory for a normal category,
Name is mandatory for a normal category
)
)
如果所有的参数都查出来,我们就会有一个Success
一个Category
值来代替。
与快速失败Either
一个有关使用合用的仿函数用于验证的好用的东西是,你可以换出你的方法来处理错误的难易程度。 如果你想在出现第一个,而不是积累他们,你基本上可以只改变你的nonNull
方法。
我们确实需要一套略有不同的进口:
import scalaz.{ Category => _, _ }
import syntax.apply._, std.either._
但目前还没有必要改变上面的例子类。
这是我们的新的验证方法:
def nonNull[A](a: A, msg: String): Either[String, A] = Option(a).toRight(msg)
几乎等同于上面的一个,但我们使用Either
代替ValidationNEL
,默认适用函子实例Scalaz提供Either
不累积误差。
这就是我们需要做的,以获得所需的快速失败行为,没有必要改变我们的buildCategory
方法。 现在,如果我们这样写:
val result: Either[String, Category] =
buildCategory(User("mary"), null, null, "Some category.")
其结果将只包含第一个错误:
Left(Parent category is mandatory for a normal category)
就如我们想要的。
附录:简要介绍一下应用性函子
假设我们有一个参数的方法:
def incremented(i: Int): Int = i + 1
并且还假设我们想将此方法应用于一些x: Option[Int]
并得到一个Option[Int]
回来。 该事实Option
是一个算符,并且因此提供了一种map
方法使这容易:
val xi = x map incremented
我们已经“解禁” incremented
到Option
函子; 也就是说,我们已经基本上改变了功能映射Int
到Int
到一个映射Option[Int]
到Option[Int]
虽然语法muddies说了一下,在“提升”的比喻在象Haskell语言更清晰) 。
现在假设我们要应用下面的add
方法,以x
和y
以类似的方式。
def add(i: Int, j: Int): Int = i + j
val x: Option[Int] = users.find(_.name == "John").map(_.age)
val y: Option[Int] = users.find(_.name == "Mary").map(_.age) // Or whatever.
这一事实Option
是一个仿函数是不够的。 这是一个单子,但是,事实是,我们可以利用flatMap
得到我们想要的东西:
val xy: Option[Int] = x.flatMap(xv => y.map(add(xv, _)))
或者,等价:
val xy: Option[Int] = for { xv <- x; yv <- y } yield add(xv, yv)
从某种意义上说,虽然的monadness Option
是矫枉过正此操作。 有一个简单的抽象称为适用函子,这是在两者之间一个仿函数和一个单子,我们需要提供所有机器。
请注意,这是在正式意义上在两者之间 :每一个单子是一个适用函子,每个适用函子是一个函子,但并不是每一个适用函子是一个单子,等等。
Scalaz为我们提供了一个适用函子实例Option
,所以我们可以写出如下:
import scalaz._, std.option._, syntax.apply._
val xy = (x |@| y)(add)
语法是有点古怪,但这个概念并不比以上-我们只是举起仿函数或单子的例子更复杂add
到适用函子。 如果我们有一个方法f
有三个参数,我们可以写如下:
val xyz = (x |@| y |@| z)(f)
等等。
那么,为什么有应用性函子不屑的一切,当我们已经有了单子? 首先,这是根本不可能为一些我们希望与-工作抽象的单子提供实例Validation
是一个很好的例子。
第二(和与此相关的),它只是一个坚实的发展的做法是使用最少的强大的抽象,将完成这项工作。 原则上,这可以让优化,而这否则是不可能的,但更重要的是它让我们写更多的可重复使用的代码。
我完全支持本·詹姆斯的建议,以便为空生产API的包装。 但是,编写包装时,你仍然有同样的问题。 因此,这里是我的建议。
为什么单子为什么理解? IMO的overcomplication。 这里是你如何能做到这一点:
def buildNormalCategory
( user: User, parent: Category, name: String, description: String )
: Either[ Error, Category ]
= Either.cond(
!Seq(user, parent, name, description).contains(null),
buildTrashCategory(user),
Error(Error.FORBIDDEN, "null detected")
)
或者,如果你坚持有错误信息存储参数的名称,你可以做到以下几点,这将需要更多的样板:
def buildNormalCategory
( user: User, parent: Category, name: String, description: String )
: Either[ Error, Category ]
= {
val nullParams
= Seq("user" -> user, "parent" -> parent,
"name" -> name, "description" -> description)
.collect{ case (n, null) => n }
Either.cond(
nullParams.isEmpty,
buildTrashCategory(user),
Error(
Error.FORBIDDEN,
"Null provided for the following parameters: " +
nullParams.mkString(", ")
)
)
}
如果你喜欢@Travis布朗的回答的适用函子的做法,但你不喜欢Scalaz语法或以其他方式只是不希望使用Scalaz,这里是丰富了标准库要么类一个简单的库作为一个适用函子验证: https://github.com/youdevise/eithervalidation
例如:
import com.youdevise.eithervalidation.EitherValidation.Implicits._
def buildNormalCategory(user: User, parent: Category, name: String, description: String): Either[List[Error], Category] = {
val validUser = Option(user).toRight(List("User is mandatory for a normal category"))
val validParent = Option(parent).toRight(List("Parent category is mandatory for a normal category"))
val validName = Option(name).toRight(List("Name is mandatory for a normal category"))
Right(Category)(validUser, validParent, validName).
left.map(_.map(errorString => Error(Error.FORBIDDEN, errorString)))
}
换句话说,这个函数会返回包含您的一类权利,如果所有的Eithers的是权利,否则会返回一个包含一个左的所有错误的列表,如果一个或多个是左派。
注意可以说是更Scala的上下的和较少的Haskell上下的语法,以及一个较小的文库;)
让我们假设你已经完成或者与下面的快速和肮脏的东西:
object Validation {
var errors = List[String]()
implicit class Either2[X] (x: Either[String,X]){
def fmap[Y](f: X => Y) = {
errors = List[String]()
//println(s"errors are $errors")
x match {
case Left(s) => {errors = s :: errors ; Left(errors)}
case Right(x) => Right(f(x))
}
}
def fapply[Y](f: Either[List[String],X=>Y]) = {
x match {
case Left(s) => {errors = s :: errors ; Left(errors)}
case Right(v) => {
if (f.isLeft) Left(errors) else Right(f.right.get(v))
}
}
}
}}
考虑一个验证函数返回一个非此即彼:
def whenNone (value: Option[String],msg:String): Either[String,String] =
if (value isEmpty) Left(msg) else Right(value.get)
一个一个curryfied构造函数返回一个元组:
val me = ((user:String,parent:String,name:String)=> (user,parent,name)) curried
你可以验证它:
whenNone(None,"bad user")
.fapply(
whenNone(Some("parent"), "bad parent")
.fapply(
whenNone(None,"bad name")
.fmap(me )
))
没有大碍。