对单个对象进行多次验证?

时间:2018-03-21 21:25:43

标签: scala scalaz applicative

scalaz验证有+++,它会累积错误和成功。但是,我的成功类型不是F[T] Semigroup[F],而是T(除非我使用Id半群......)。基本上我只想积累错误。有这样的方法吗?

即。我有一个List[A => ValidationNel[Err, A]],我想将所有这些功能应用到一个A并获取ValidationNel[Err, A]

2 个答案:

答案 0 :(得分:0)

有很多选择。如果您将验证作为列表,则:

import scalaz.syntax.traverse._

val result: ValidationNel[Err, A] =
  validations.traverse(_(a)).map(Function.const(a))

会帮助你。但是,如果您具有命名的验证,则可能会使用*>(或<*取决于首选项)获得更具可读性的代码:

import scalaz.syntax.applicative._

validateName(a) *> validateOther1(a) *> validateOther2(a)

答案 1 :(得分:0)

我们需要一些声明:

  • 一个人可以使用|+|操作符来附加形成一个半群的类型的实例。
  • 如果每个应用程序的参数类型都有一个半组实例,则每个应用程序都有一个半组实例。
  • ValidationNel[E,A]有一个适用实例(类型参数:A)。
  • 功能1:X => A有一个适用实例(类型参数:A)。

因此,如果我们有几个类型为ValidationNel[E,A]的变量,并且A有一个半群,那么我们可以像这样追加它们:

val result: ValidationNel[E,A] = validationResult1 |+|
                                 validationResult2 |+|
                                 validationResult3

另一方面,如果我们有几个类型为A => ValidationNel[E,A]的函数,我们也可以附加它们(因为ValidationNel[E,A]有一个半群)。如果我们用A代替String,它可能看起来像:

val isNonEmpty: String => ValidationNel[String,String] =
  str => Validation.liftNel(str)(_.isEmpty, "String is empty")
val max10Characters: String => ValidationNel[String,String] =
  str => Validation.liftNel(str)(_.length > 10, "More than 10 characters")
val startsWithUpper: String => ValidationNel[String,String] =
  str => Validation.liftNel(str)(_.headOption.exists(!_.isUpper), "Doesn't start with capital")

val validateEverything: String => ValidationNel[String,String] =
  isNonEmpty |+| max10Characters |+| startsWithUpper

或者在列表的情况下,我们可以使用suml函数,该函数在隐式Semigroup实例上具有引用(等效):

val validateEverything: String => ValidationNel[String,String] =
  NonEmptyList(isNonEmpty, max10Characters, startsWithUpper).suml

唯一的问题是,大多数向内类型参数(String)的默认半组是字符串连接(请参见scalaz.std.StringInstances.stringInstance#append),如果A是任意类型,则可能存在完全没有半组实例。我们需要提供一个,在我们的例子中,“采用第一个值”半组就可以了:

implicit val takeFirst: Semigroup[A] = (a1: A, a2: Any) => a1

以上所有内容共同为我们提供了完整的测试代码:

import org.scalatest.{FreeSpec, Matchers}
import scalaz.{NonEmptyList, Semigroup, Validation, ValidationNel}
import scalaz.syntax.semigroup._
import scalaz.syntax.validation._
import scalaz.syntax.foldable1._
import scalaz.std.AllInstances.function1Semigroup

class ValidationSpec extends FreeSpec with Matchers {
  "accumulate validations" in {
    type A  = String
    type VF = A => ValidationNel[String, A]
    implicit val takeFirst: Semigroup[A] = (f1: A, f2: Any) => f1

    val isNonEmpty: VF =
      str => Validation.liftNel(str)(_.isEmpty, "String is empty")
    val max10Characters: VF =
      str => Validation.liftNel(str)(_.length > 10, "More than 10 characters")
    val startsWithUpper: VF =
      str => Validation.liftNel(str)(_.headOption.exists(!_.isUpper), "Doesn't start with capital")

    val allValidations = isNonEmpty |+| max10Characters |+| startsWithUpper
    allValidations("") shouldBe NonEmptyList("String is empty").failure
    allValidations("c") shouldBe NonEmptyList("Doesn't start with capital").failure
    allValidations("c2345678901") shouldBe NonEmptyList("More than 10 characters", "Doesn't start with capital").failure
    allValidations("Good value") shouldBe "Good value".successNel

    val listValidations  = NonEmptyList(isNonEmpty, max10Characters, startsWithUpper)
    val foldedValidation = listValidations.suml1
    foldedValidation("") shouldBe NonEmptyList("String is empty").failure
    foldedValidation("c") shouldBe NonEmptyList("Doesn't start with capital").failure
    foldedValidation("c2345678901") shouldBe NonEmptyList("More than 10 characters", "Doesn't start with capital").failure
    foldedValidation("Good value") shouldBe "Good value".successNel
  }
}