scala:如何以功能方式处理验证 [英] scala: how to handle validations in a functional way

查看:171
本文介绍了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 ))}}

那就是我想像一个Option [T]如果任何验证失败,而不是返回无它给我的错误列表...



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

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



-



oops,刚刚发布了问题并发现了



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



无论如何,我想知道什么你考虑所选择的方法,如果还有其他更好的选择来处理它

解决方案

使用scalaz你有验证[E,A] ,就像 [E,A] ,但具有如果 E 是一个半群(意味着可以连接的东西,比如列表)比多个验证的结果可以组合起来,以保持所有错误发生。



使用Scala 2.10-M6和Scalaz 7.0.0-M2作为例子,其中Scalaz具有自定义 [L,R] 命名为 \ / [L,R] 默认情况下是有偏见的:

  import scalaz._,Scalaz._ 

隐式类EitherPimp [E,A](val e:E \\ \\ / A)扩展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$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $



$ b $ p>这里结果是一个验证[NonEmptyList [Throwable],String] ,包含所有的错误(temp传感器错误和/或twitter错误或无)或成功的消息。然后,您可以切换回 \ /



注意:两者和验证之间的区别主要是通过验证,您可以累积错误,但不能 flatMap 丢失累积的错误,而您无法(轻松)累积,但可以 flatMap (或以理解为准),可能会丢失第一条错误消息。



关于错误层次结构



我认为这可能是你的兴趣。不管使用scalaz / / \ / / 验证,我体验到入门很容易,但是前进的还需要一些额外的工作。问题是,如何以有意义的方式从多个错误函数中收集错误?当然,你可以使用 Throwable List [String] 到处都有一个简单的时间,但是听不到太多的可用或可解释想象一下,收到孩子年龄缺失的错误列表:IO错误读取文件::除以零。



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

 对象错误{

对象gamestart {
密封特征错误
case类ResourceError(e:errors.resource.Error)extends错误
案例类WordSourceError(e:errors.wordsource.Error)扩展错误
}

对象资源{
案例类错误(e:GdxRuntimeException)
}

对象词源{
案例类错误(e:/ *丑陋* /任何)
}

}

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

 为{
wordSource< -
错误。 gamestart.WordSourceError< - :
errors.wordsource.Error< - :
wordSourceCreator.doCreateWordSource(mtRandom).catchLeft.unsafePerformIO.toEither

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

} yield ...

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



可以通过提供无形 HListIso s可以绘制好的错误树。



修订



更新:(e:\ /)。vnel 将故障侧提升为一个 NonEmptyList 所以如果我们有一个失败,我们至少有一个错误(是或否)。


I'm developing a method that is supossed to persist an object, if it passes a list of conditions

if any (or many) condition fail (or any other kind of error appear), a list with the errors should be returned, if everything goes well, a saved entity should be returned

I was thinking about something like this (it's pseudocode, of course)

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

that is, I'd like to treat it like an Option[T], that if any validation fails, instead of returning None it gives me the list of errors...

The idea is to return an array of json errors...

So the question would be, is this the right way to handle these kind of situation? and what would be the way to accomplish it in scala?

--

oops, just posted the question and discovered Either

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

anyway, I'd like to know what you think about the chosen approach, and if there's any other better alternative to handle it

解决方案

Using scalaz you have Validation[E, A], which is like Either[E, A] but has the property that if E is a semigroup (meaning things that can be concatenated, like lists) than multiple validated results can be combined in a way that keeps all the errors that occured.

Using Scala 2.10-M6 and Scalaz 7.0.0-M2 for example, where Scalaz has a custom Either[L, R] named \/[L, R] which is right-biased by default:

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

Here result is a Validation[NonEmptyList[Throwable], String], either containing all the errors occured (temp sensor error and/or twitter error or none) or the successful message. You can then switch back to \/ for convenience.

Note: The difference between Either and Validation is mainly that with Validation you can accumulate errors, but cannot flatMap to lose the accumulated errors, while with Either you can't (easily) accumulate but can flatMap (or in a for-comprehension) and possibly lose all but the first error message.

About error hierarchies

I think this might be of interest for you. Regardless of using scalaz/Either/\//Validation, I experienced that getting started was easy but going forward needs some additional work. The problem is, how do you collect errors from multiple erring functions in a meaningful way? Sure, you can just use Throwable or List[String] everywhere and have an easy time, but doesn't sound too much usable or interpretable. Imagine getting a list of errors like "child age missing" :: "IO error reading file" :: "division by zero".

So my choice is to create error hierarchies (using ADT-s), just like as one would wrap checked exceptions of Java into hierarchies. For example:

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

}

Then when using result of erring functions with different error types, I join them under a relevant parent error type.

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

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

} yield ...

Here f <-: e maps the function f on the left of e: \/ since \/ is a Bifunctor. For se: scala.Either you might have se.left.map(f).

This may be further improved by providing shapeless HListIsos to be able to draw nice error trees.

Revisions

Updated: (e: \/).vnel lifts the failure side into a NonEmptyList so if we have a failure we have at least one error (was: or none).

这篇关于scala:如何以功能方式处理验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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