当前位置: 首页 > 知识库问答 >
问题:

Scala中的方法参数验证,用于理解和monads

叶华皓
2023-03-14

我试图验证方法的参数是否为空,但我没有找到解决方案。。。

有人可以告诉我该怎么做吗?

我正在尝试这样的事情:

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

共有3个答案

龙令雪
2023-03-14

如果您喜欢@Travis Brown答案中的应用函子方法,但不喜欢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)))
}

换句话说,如果所有的分类都是正确的,这个函数将返回一个包含您的分类的正确分类,或者如果一个或多个分类是错误的,它将返回一个包含所有错误列表的错误分类。

请注意更像Scala、更像Haskell的语法,以及更小的库;)

翟鸿振
2023-03-14

我完全支持 Ben James 的建议,为生成 null 的 api 创建一个包装器。但是在编写该包装器时,您仍然会遇到同样的问题。所以这是我的建议。

为什么是单子?为什么是为了理解?在我看来,这是一个过度复杂的问题。你可以这样做:

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(", ")
      )
    )
  }
宰宣
2023-03-14

如果你愿意使用Scalaz,它有一些工具可以使这种任务更方便,包括一个新的验证类和一些有用的右偏置类型类实例,用于普通的旧scala。要么。我将在这里给出每个示例。

首先是我们的Scalaz导入(注意,我们必须隐藏< code>scalaz。类别以避免名称冲突):

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)

请注意,ValidationNel[String,_]不是monad(例如,出于此处讨论的原因),但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
  )
)

如果所有参数都已签出,则我们将改为使用“类别”的“成功”。

使用应用程序函子进行验证的一个方便之处是,您可以轻松地交换处理错误的方法。如果您希望第一次失败,而不是累积失败,您基本上只需更改非空方法即可。

我们确实需要一组略有不同的导入:

import scalaz.{ Category => _, _ }
import syntax.apply._, std.either._

但是没有必要更改上面的case类。

这是我们的新验证方法:

def nonNull[A](a: A, msg: String): Either[String, A] = Option(a).toRight(msg)

与上面的一个几乎相同,除了我们使用<code>或<code>而不是<code>ValidationNEL</code>之外,Scalaz为<code>或者<code>提供的默认应用函子实例不会累积错误。

这就是我们要获得期望的快速失败行为所需要做的一切——不需要对我们的< code>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

我们“提升”了递增选项函子中;也就是说,我们从本质上把一个函数映射<code>Int</code>到<code>Int</code>变成了一个映射<code>Option[Int]Option[Int](虽然语法有点混乱,但在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是一个仿函数这一事实是不够的。然而,它是一个单子的事实是,我们可以使用平面图来得到我们想要的:

val xy: Option[Int] = x.flatMap(xv => y.map(add(xv, _)))

或者,等效地:

val xy: Option[Int] = for { xv <- x; yv <- y } yield add(xv, yv)

但是,从某种意义上说,Option的一元性对于此操作来说是过度的。有一个更简单的抽象 -- 称为应用函子 – 它介于函子和 monad 之间,它提供了我们需要的所有机制。

请注意,它在形式意义上介于两者之间:每个单子都是应用仿函数,每个应用仿函数都是仿函数,但不是每个应用仿函数都是单子,等等。

Scalaz为Option提供了一个实用的仿函数实例,因此我们可以编写以下内容:

import scalaz._, std.option._, syntax.apply._

val xy = (x |@| y)(add)

语法有点奇怪,但这个概念并不比上面的函子或单子示例复杂。我们只是将<code>add</code>提升到应用函子中。如果我们有一个带有三个参数的方法f,我们可以编写以下代码:

val xyz = (x |@| y |@| z)(f)

等等。

那么,当我们有monads时,为什么要为应用函子而烦恼呢?首先,根本不可能为我们想要使用的一些抽象提供 monad 实例 — 验证就是一个完美的例子。

其次(也是相关的),使用最不强大的抽象来完成工作是一种可靠的开发实践。原则上,这可能允许优化,否则是不可能的,但更重要的是,它使我们编写的代码更可重用。

 类似资料:
  • 我是Spring MVC的新手,我已经导入了一个与服务器端验证相关的教程项目,我对它到底如何工作有些怀疑。 因此,我有一个名为login.jsp的登录页面,其中包含以下登录表单: 因此,如您所见,在username和password字段上声明了@notblank和@size验证注释。 这里的第一个疑问是:与两个常用库javax.validation和org.hibernate.validator到

  • 我对Scala和Spark RDD编程相当陌生。我使用的数据集是一个CSV文件,其中包含电影列表(每部电影一行)及其关联的用户评分(以逗号分隔的评分列表)。CSV中的每一列都代表一个不同的用户,以及他/她对电影的评价。因此,用户1对每部电影的评分在左侧第二列中表示: 示例输入: 蜘蛛侠,1,2,3,3 睡眠博士,4,4,,1 我得到以下错误: 当我执行下面的几行时。对于下面的程序,第二行代码拆分由

  • 我在Scala 2.10中遇到了语法问题,无法理解。 < code>for(a 那么为什么

  • 每个人 因此,我有一个SpringBoot应用程序,它带有一个控制器,该控制器有几种方法,将以下POJO作为参数: 对于其中一个控制器endpoint,我想应用额外的验证逻辑,因此在我添加了以下内容: 是我想要应用的约束注释。 我的问题是,只有在中定义的检查成功通过时,才会调用此附加约束。如果为空,约束将被忽略,客户端将收到不完整的验证结果。我错过了什么?

  • 我有两个函数返回期货。我正试图使用for-yield理解将第一个函数的修改结果输入到另一个函数中。 此方法有效: 然而,我对“如果”在那里不满意,似乎我应该能够使用地图。 但是当我尝试使用地图时: 我得到一个编译错误: 我尝试了一些变化,但没有发现任何有吸引力的工作。有人能提出更好的理解和/或解释我的第二个例子的错误吗? 下面是一个最小但完整的Scala 2.10可运行示例:

  • 本文向大家介绍.Net Web Api中利用FluentValidate进行参数验证的方法,包括了.Net Web Api中利用FluentValidate进行参数验证的方法的使用技巧和注意事项,需要的朋友参考一下 前言 本文主要介绍了关于.Net Web Api用FluentValidate参数验证的相关内容,下面话不多说了,来一起看看详细的介绍吧。 方法如下 安装FluentValidate