Scala中的方法参数验证,用于理解和单子 [英] Method parameters validation in Scala, with for comprehension and monads

查看:128
本文介绍了Scala中的方法参数验证,用于理解和单子的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to validate the parameters of a method for nullity but i don't find the solution...

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

Can someone tell me how to do?

我正在尝试这样的事情:

I'm trying something like this:

  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实例.我会在这里举例说明.

If you're willing to use Scalaz, it has a handful of tools that make this kind of task more convenient, including a new Validation class and some useful right-biased type class instances for plain old scala.Either. I'll give an example of each here.

首先导入我们的Scalaz(请注意,我们必须隐藏scalaz.Category以避免名称冲突):

First for our Scalaz imports (note that we have to hide scalaz.Category to avoid the name conflict):

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

在此示例中,我使用的是Scalaz 7.您需要进行一些小的更改才能使用6.

I'm using Scalaz 7 for this example. You'd need to make some minor changes to use 6.

我假设我们有这个简化的模型:

I'll assume we have this simplified model:

case class User(name: String)
case class Category(user: User, parent: Category, name: String, desc: String)

接下来,我将定义以下验证方法,如果您采用不涉及检查空值的方法,则可以轻松地采用该方法:

Next I'll define the following validation method, which you can easily adapt if you move to an approach that doesn't involve checking for null values:

def nonNull[A](a: A, msg: String): ValidationNel[String, A] =
   Option(a).toSuccess(msg).toValidationNel

Nel部分代表非空列表",ValidationNel[String, A]本质上与Either[List[String], A]相同.

The Nel part stands for "non-empty list", and a ValidationNel[String, A] is essentially the same as an Either[List[String], A].

现在,我们使用此方法检查参数:

Now we use this method to check our arguments:

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(例如,出于此处所述的原因),但是可应用的函子,当我们将Category.apply提升到其中时,我们将在这里使用该事实.有关适用函子的更多信息,请参见下面的附录.

Note that Validation[Whatever, _] isn't a monad (for reasons discussed here, for example), but ValidationNel[String, _] is an applicative functor, and we're using that fact here when we "lift" Category.apply into it. See the appendix below for more information on applicative functors.

现在,如果我们写这样的话:

Now if we write something like this:

val result: ValidationNel[String, Category] = 
  buildCategory(User("mary"), null, null, "Some category.")

我们将因累积的错误而失败:

We'll get a failure with the accumulated errors:

Failure(
 NonEmptyList(
   Parent category is mandatory for a normal category,
   Name is mandatory for a normal category
  )
)

如果所有参数都已签出,我们将使用SuccessCategory值代替.

If all of the arguments had checked out, we'd have a Success with a Category value instead.

使用应用函子进行验证的一项便利操作是,您可以轻松地交换处理错误的方法.如果您想在第一次失败而不是积累失败,则可以基本上更改nonNull方法.

One of the handy things about using applicative functors for validation is the ease with which you can swap out your approach to handling errors. If you want to fail on the first instead of accumulating them, you can essentially just change your nonNull method.

我们确实需要一组略有不同的进口商品:

We do need a slightly different set of imports:

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

但是没有必要更改上面的案例类.

But there's no need to change the case classes above.

这是我们的新验证方法:

Here's our new validation method:

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

与上面的代码几乎相同,除了我们使用的是Either而不是ValidationNEL,并且Scalaz为Either提供的默认应用函子实例不会累积错误.

Almost identical to the one above, except that we're using Either instead of ValidationNEL, and the default applicative functor instance that Scalaz provides for Either doesn't accumulate errors.

这就是我们想要获得所需的快速故障行为所需要做的所有工作-无需更改buildCategory方法.现在,如果我们这样写:

That's all we need to do to get the desired fail-fast behavior—no changes are necessary to our buildCategory method. Now if we write this:

val result: Either[String, Category] =
  buildCategory(User("mary"), null, null, "Some category.")

结果将仅包含第一个错误:

The result will contain only the first error:

Left(Parent category is mandatory for a normal category)

正是我们想要的.

假设我们有一个带有单个参数的方法:

Suppose we have a method with a single argument:

def incremented(i: Int): Int = i + 1

并且还假设我们想将此方法应用于某些x: Option[Int]并取回Option[Int]. Option是函子,因此提供了map方法,这一事实使此操作变得容易:

And suppose also that we want to apply this method to some x: Option[Int] and get an Option[Int] back. The fact that Option is a functor and therefore provides a map method makes this easy:

val xi = x map incremented

我们已经将incremented提升到Option函子中;也就是说,我们实质上已经将映射IntInt的函数更改为映射到Option[Int]Option[Int]的函数(尽管语法有点混淆了—在类似"Haskell).

We've "lifted" incremented into the Option functor; that is, we've essentially changed a function mapping Int to Int into one mapping Option[Int] to Option[Int] (although the syntax muddies that up a bit—the "lifting" metaphor is much clearer in a language like Haskell).

现在假设我们想以类似的方式将以下add方法应用于xy.

Now suppose we want to apply the following add method to x and y in a similar fashion.

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来获得我们想要的东西:

The fact that Option is a functor isn't enough. The fact that it's a monad, however, is, and we can use flatMap to get what we want:

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的单调对于此操作来说是过大的.有一个更简单的抽象-称为 applicative 仿函数-介于仿函数和monad之间,并且提供了我们所需的所有机械.

In a sense, though, the monadness of Option is overkill for this operation. There's a simpler abstraction—called an applicative functor—that's in-between a functor and a monad and that provides all the machinery we need.

请注意,从正式意义上讲,它是介于它们之间:每个单子是一个可应用的函子,每个可应用的函子是一个函子,但并非每个应用的函子都是一个单子,等等.

Note that it's in-between in a formal sense: every monad is an applicative functor, every applicative functor is a functor, but not every applicative functor is a monad, etc.

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

Scalaz gives us an applicative functor instance for Option, so we can write the following:

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

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

语法有点奇怪,但是概念并不比上面的functor或monad示例更复杂-我们只是将add提升到适用的functor中.如果我们有一个带有三个参数的方法f,我们可以编写以下代码:

The syntax is a little odd, but the concept isn't any more complicated than the functor or monad examples above—we're just lifting add into the applicative functor. If we had a method f with three arguments, we could write the following:

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

以此类推.

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

So why bother with applicative functors at all, when we've got monads? First of all, it's simply not possible to provide monad instances for some of the abstractions we want to work with—Validation is the perfect example.

第二(及相关),使用最小强大的抽象来完成工作只是一种可靠的开发实践.原则上,这可能允许进行其他优化,但是更重要的是,它使我们编写的代码更具可重用性.

Second (and relatedly), it's just a solid development practice to use the least powerful abstraction that will get the job done. In principle this may allow optimizations that wouldn't otherwise be possible, but more importantly it makes the code we write more reusable.

这篇关于Scala中的方法参数验证,用于理解和单子的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆