方法参数验证Scala中,与理解和单子(Method parameters validation i

2019-06-18 01:18发布

我想确认婚姻无效方法的参数,但我没有找到解决方案...

谁能告诉我该怎么办?

我想是这样的:

  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) )
    }
  }

Answer 1:

如果您愿意使用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

我们已经“解禁” incrementedOption函子; 也就是说,我们已经基本上改变了功能映射IntInt到一个映射Option[Int]Option[Int]虽然语法muddies说了一下,在“提升”的比喻在象Haskell语言更清晰) 。

现在假设我们要应用下面的add方法,以xy以类似的方式。

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是一个很好的例子。

第二(和与此相关的),它只是一个坚实的发展的做法是使用最少的强大的抽象,将完成这项工作。 原则上,这可以让优化,而这否则是不可能的,但更重要的是它让我们写更多的可重复使用的代码。



Answer 2:

我完全支持本·詹姆斯的建议,以便为空生产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(", ")
      )
    )
  }


Answer 3:

如果你喜欢@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上下的语法,以及一个较小的文库;)



Answer 4:

让我们假设你已经完成或者与下面的快速和肮脏的东西:

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 )
   ))

没有大碍。



文章来源: Method parameters validation in Scala, with for comprehension and monads