scalaz验证有+++
,它会累积错误和成功。但是,我的成功类型不是F[T]
Semigroup[F]
,而是T
(除非我使用Id
半群......)。基本上我只想积累错误。有这样的方法吗?
即。我有一个List[A => ValidationNel[Err, A]]
,我想将所有这些功能应用到一个A
并获取ValidationNel[Err, A]
。
答案 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
)。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
}
}