我正在开发一种方法,如果该对象通过条件列表,则该方法应该保留该对象。

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

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

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

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

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

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

--

糟糕,刚刚发布了问题并发现了

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

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

最佳答案

使用scalaz可以得到Validation[E, A],它类似于Either[E, A],但是具有以下属性:如果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}"
}

此处resultValidation[NonEmptyList[Throwable], String],包含所有发生的错误(温度传感器错误和/或twitter错误或没有错误)或成功的消息。然后,为方便起见,您可以切换回\/

注意:Either和Validation之间的区别主要在于,使用Validation可以累积错误,但不能flatMap丢失累积的错误,而使用Either则不能(轻松)累积但可以flatMap(或出于理解),并且可能会丢失所有内容,但第一个错误消息除外。

关于错误层次结构

我认为这可能会让您感兴趣。不管使用scalaz/Either/\//Validation,我都觉得入门很容易,但是继续需要一些额外的工作。问题是,如何以有意义的方式从多个错误函数中收集错误?当然,您可以只在任何地方使用ThrowableList[String]并拥有轻松的时间,但是听起来并不太有用或无法解释。想象得到一个错误列表,例如“缺少 child 年龄”::“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)
  }

}

然后,当使用具有不同错误类型的错误函数的结果时,我将它们加入相关的父错误类型下。
for {
  wordSource <-
    errors.gamestart.WordSourceError <-:
    errors.wordsource.Error <-:
    wordSourceCreator.doCreateWordSource(mtRandom).catchLeft.unsafePerformIO.toEither

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

} yield ...

由于f <-: e是Bifunctor,此处f将函数e: \/映射到\/的左侧。对于se: scala.Either,您可能拥有se.left.map(f)

通过提供shapeless HListIso可以绘制漂亮的错误树,可以进一步改善这一点。

修订版

更新:(e: \/).vnel将失败方面提升为NonEmptyList,因此,如果我们遇到失败,则至少会有一个错误(是:或无)。

关于Scala:如何以功能方式处理验证,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/12067296/

10-12 07:18