scala - Scala中的方法参数验证,用于理解和单子(monad)

标签 scala monads for-comprehension either

我正在尝试验证方法的参数是否为空,但找不到解决方案...

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

我正在尝试这样的事情:

  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, _]不是monad(例如,出于讨论here的原因),但是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
  )
)

如果所有参数都已 checkout ,则我们将使用带有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]的函数(尽管语法有点混淆了—在类似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是函子的事实还不够。但是,它是一个monad的事实是,我们可以使用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)

但是,从某种意义上说,Option的单调对于此操作而言是过大的。在函子和monad之间有一个更简单的抽象(称为应用函子),它提供了我们需要的所有机械。

请注意,这是介于两者之间的形式:每个monad是一个应用函子,每个applicative函子都是函子,但不是每个applicative函子都是monad,依此类推。

Scalaz为我们提供了Option的应用仿函数实例,因此我们可以编写以下代码:
import scalaz._, std.option._, syntax.apply._

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

语法有点奇怪,但是概念并不比上面的functor或monad示例更复杂-我们只是将add提升为适用的functor。如果我们有一个带有三个参数的f方法,则可以编写以下代码:
val xyz = (x |@| y |@| z)(f)

等等。

那么,当我们有了单子(monad)时,为什么还要烦恼所有应用函子呢?首先,根本不可能为我们要使用的某些抽象提供monad实例-Validation是完美的示例。

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

关于scala - Scala中的方法参数验证,用于理解和单子(monad),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12307965/

相关文章:

scala - 在 Scala 的 O(sqrt(n)) 中检查数字是否为质数

scala - 在 Scala 中用于理解性评估的奇怪 (?)

scala - 验证 sbt-Assembly IntelliJ 中的着色

jquery - 使用 jquery 函数在 lift 框架片段中显示 div

scala - 查询参数作为列表

scala - 在 Scala 中,我可以仅将某些文字隐式转换为我的自定义类型吗?

haskell - Control.MonadPlus.Free 出了什么问题?

haskell - liftM 函数是否被剥夺了它们的一元本质?

clojure - 如何更好地迭代 Clojure 中的状态(monad?)

scala - Scala 中无限流的嵌套迭代