Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/search/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Scala中的方法参数验证,用于理解和单子_Scala_Monads_For Comprehension_Either - Fatal编程技术网

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

Scala中的方法参数验证,用于理解和单子,scala,monads,for-comprehension,either,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

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

有人能告诉我怎么做吗

我正在尝试这样的事情:

  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) )
    }
  }
val result: ValidationNel[String, Category] = 
  buildCategory(User("mary"), null, null, "Some category.")
def buildNormalCategory(用户:用户,父级:类别,名称:字符串,描述:字符串):任一[错误,类别]={
val错误:选项[String]=for{

_我完全支持Ben James关于为空生成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(", ")
      )
    )
  }
如果您愿意使用,它有一些工具可以使这类任务更加方便,包括一个新的
验证
类和一些用于普通的
scala的有用的右偏类型类实例

通过
验证累积错误
首先是Scalaz导入(请注意,我们必须隐藏
Scalaz.Category
,以避免名称冲突):

在这个例子中,我使用的是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]
本质上与
或[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,\u]
不是单子(例如,出于讨论的原因),但是
ValidationNel[String,\u]
是一个应用函子,我们在这里“提升”
类别时使用了这一事实。将
应用到其中。有关应用函子的更多信息,请参阅下面的附录

现在如果我们这样写:

  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) )
    }
  }
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
  )
)
如果所有的参数都已签出,我们将得到一个
成功
,而不是一个
类别

使用

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

我们确实需要一套稍微不同的导入:

import scalaz.{ Category => _, _ }
import syntax.apply._, std.either._
但是没有必要更改上面的case类

以下是我们的新验证方法:

def nonNull[A](a: A, msg: String): Either[String, A] = Option(a).toRight(msg)
与上面的一个几乎相同,只是我们使用了
other
而不是
ValidationNEL
,并且Scalaz为
other
提供的默认应用程序函子实例不会累积错误

这就是我们需要做的所有事情,以获得所需的快速故障行为,我们的
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
选项
函子;也就是说,我们本质上已经将一个函数映射
Int
Int
变成了一个映射
选项[Int]
选项[Int]
(尽管语法把“提升”弄得有点混乱)在哈斯克尔(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)
语法有点奇怪,但是这个概念并不比上面的函子或单子示例复杂,我们只是将
add
提升到applicative函子中。如果我们有一个带有三个参数的方法
f
,我们可以编写以下代码:

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

val xy = (x |@| y)(add)
val xyz = (x |@| y |@| z)(f)
等等

既然我们有了monad,为什么还要麻烦应用程序函子呢?首先,我们不可能为我们想要使用的一些抽象提供monad实例-
验证是一个完美的例子


第二(和相关的),这只是一种可靠的开发实践,使用功能最差的抽象来完成工作。原则上,这可能会允许进行本来不可能实现的优化,但更重要的是,它使我们编写的代码更加可重用。

如果您喜欢@Travis Brown答案中的applicative functor方法,但不喜欢Scalaz语法或其他只是不想使用Scalaz,这里有一个简单的库,它丰富了标准库中的任何一个类,用作应用程序函子验证:

例如:

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都是Rights,这个函数将返回一个包含您的类别的Right,或者如果一个或多个是Left,它将返回一个包含所有错误列表的Left


请注意,可以说更具Scala-ish和更少的Haskell-ish语法,以及更小的库;)

让我们假设您已经完成了以下快速而肮脏的内容之一:

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构造函数返回
   whenNone(None,"bad user") 
   .fapply(
   whenNone(Some("parent"), "bad parent") 
   .fapply(
   whenNone(None,"bad name") 
   .fmap(me )
   ))