scala:如何以功能方式处理验证

ope*_*sas 7 error-handling scala

我正在开发一种方法,如果它传递一个条件列表,它应该持久化一个对象.

如果任何(或许多)条件失败(或出现任何其他类型的错误),则应返回包含错误的列表,如果一切顺利,则应返回已保存的实体.

我在想这样的事情(当然是伪代码):

request.body.asJson.map { json =>
  json.asOpt[Wine].map { wine =>
    wine.save.map { wine => 
      Ok(toJson(wine.update).toString)
    }.getOrElse  { errors => BadRequest(toJson(errors))}
  }.getOrElse    { BadRequest(toJson(Error("Invalid Wine entity")))}
}.getOrElse      { BadRequest(toJson(Error("Expecting JSON data")))}
Run Code Online (Sandbox Code Playgroud)

也就是说,我想将它视为一个Option [T],如果任何验证失败,而不是返回None它给我一个错误列表......

想法是返回一个JSON错误数组......

所以问题是,这是处理这种情况的正确方法吗?在Scala中实现它的方法是什么?

-

哎呀,刚刚发布了问题并发现了

http://www.scala-lang.org/api/current/scala/Either.html

无论如何,我想知道你对所选方法的看法,以及是否还有其他更好的选择来处理它.

ron*_*ron 13

使用scalaz Validation[E, A],就像Either[E, A]但是具有if E半群(意思是可以连接的东西,如列表)的属性,而不是多个验证结果可以以保持发生的所有错误的方式组合.

例如,使用Scala 2.10-M6和Scalaz 7.0.0-M2,其中Scalaz具有默认情况下右偏置的自定义Either[L, R]命名\/[L, R]:

import scalaz._, Scalaz._

implicit class EitherPimp[E, A](val e: E \/ A) extends AnyVal {
  def vnel: ValidationNEL[E, A] = e.validation.toValidationNEL
}

def parseInt(userInput: String): Throwable \/ Int = ???
def fetchTemperature: Throwable \/ Int = ???
def fetchTweets(count: Int): Throwable \/ List[String] = ???

val res = (fetchTemperature.vnel |@| fetchTweets(5).vnel) { case (temp, tweets) =>
  s"In $temp degrees people tweet ${tweets.size}"
}
Run Code Online (Sandbox Code Playgroud)

result是一个Validation[NonEmptyList[Throwable], String],包含发生的所有错误(临时传感器错误和/或推特错误或无)或成功消息.然后,您可以切换回来以\/方便使用.

注意:Either和Validation之间的区别主要在于验证可以累积错误,但不能flatMap丢失累积的错误,而使用或者你不能(轻松)累积但可以flatMap(或者在for-comprehension中)并且可能丢失除了第一条错误消息外

关于错误层次结构

我想这可能对你有用.无论使用scalaz/Either/ \// Validation,我都意识到入门很容易但是前进需要一些额外的工作.问题是,如何以有意义的方式从多个错误的函数中收集错误?当然,你可以只使用ThrowableList[String]无处不在,有一个简单的时间,但听起来并不太多可用或可解释的.想象一下,得到一个错误列表,如"儿童年龄缺失"::"IO错误读取文件"::"除以零".

所以我的选择是创建错误层次结构(使用ADT-s),就像将Java的已检查异常包装到层次结构中一样.例如:

object errors {

  object gamestart {
    sealed trait Error
    case class ResourceError(e: errors.resource.Error) extends Error
    case class WordSourceError(e: errors.wordsource.Error) extends Error
  }

  object resource {
    case class Error(e: GdxRuntimeException)
  }

  object wordsource {
    case class Error(e: /*Ugly*/ Any)
  }

}
Run Code Online (Sandbox Code Playgroud)

然后,当使用具有不同错误类型的错误函数的结果时,我在相关的父错误类型下加入它们.

for {
  wordSource <-
    errors.gamestart.WordSourceError <-:
    errors.wordsource.Error <-:
    wordSourceCreator.doCreateWordSource(mtRandom).catchLeft.unsafePerformIO.toEither

  resources <-
    errors.gamestart.ResourceError <-:
    GameViewResources(layout)

} yield ...
Run Code Online (Sandbox Code Playgroud)

这里f <-: e映射f左边的函数,e: \/因为\/是Bifunctor.因为se: scala.Either你可能有se.left.map(f).

这可以通过提供无形 HListIso s来进一步改进,以便能够绘制漂亮的错误树.

修订

更新:(e: \/).vnel将故障侧提升为一个NonEmptyList如果我们发生故障,我们至少有一个错误(是:或者没有).


par*_*tic 2

Either[A,Option[B]]如果您需要一个空值,可以使用 lift来代替使用Box,它可以具有三个值:

  • Full(有有效结果)
  • Empty(没有结果,但也没有错误)
  • Failure(发生错误)

BoxEither得益于丰富的 API,它们更加灵活。当然,虽然它们是为 Lift 创建的,但您可以在任何其他框架中使用它们。