问题
I'm trying to use Scalaz 7 Validation in my app. However, I'm having an issue getting the |@|
applicative functor to coalesce my failures. Here's the code I have:
type ValidationResult = ValidationNel[String, Unit]
def validate[A: ClassTag](instance: A, fieldNames: Option[Seq[String]] = None): ValidationResult = {
val fields = classTag[A].runtimeClass.getDeclaredFields
val fieldSubset = fieldNames match {
case Some(names) => fields.filter { field => names.contains(field.getName) }
case None => fields
}
fieldSubset.map {
field => field.getAnnotations.toSeq.map {
field.setAccessible(true)
val (name, value) = (field.getName, field.get(instance))
field.setAccessible(false)
annotation => annotation match {
case min: Min => minValidate(name, value, min.value())
case size: Size => sizeValidate(name, value, size.min(), size.max())
}
}
}.flatten[ValidationResult].foldLeft(().successNel[String])(_ |@| _)
}
The minValidate
and sizeValidate
functions just return ValidationResults
.
The problem is, this code won't compile. The error message is:
Type mismatch, expected F0.type#M[NotInferedB], actual: ValidationResult
I have no idea what that means... do I need to give Scala more type info?
What I'm trying to accomplish is, if all fields are successNel
s, then return that, otherwise, return a combination of all the failureNel
s.
Has |@|
changed since previous version of Scalaz? Because even if I do something like:
().successNel |@| ().successNel
I get the same error.
Update
I started poking around the Scalaz source and I found the +++
which seems to do what I want.
What's the difference between +++
and |@|
?
回答1:
Scalaz's applicative builder syntax (|@|
) gives you a way of "lifting" functions into an applicative functor. Suppose we have the following results, for example:
val xs: ValidationNel[String, List[Int]] = "Error!".failNel
val ys: ValidationNel[String, List[Int]] = List(1, 2, 3).success
val zs: ValidationNel[String, List[Int]] = List(4, 5).success
We can lift the list concatenation function (++
) into the Validation
like this:
scala> println((ys |@| zs)(_ ++ _))
Success(List(1, 2, 3, 4, 5))
scala> println((xs |@| ys)(_ ++ _))
Failure(NonEmptyList(Error!))
scala> println((xs |@| xs)(_ ++ _))
Failure(NonEmptyList(Error!, Error!))
This syntax is a little weird—it's very unlike how you lift functions into an applicative functor in Haskell, for example, and is designed this way primarily to outsmart Scala's fairly stupid type inference system. See my answer here or blog post here for more discussion.
One part of the weirdness is that xs |@| ys
doesn't really mean anything on its own—it's essentially an argument list that's waiting to be applied to a function that it will lift into its applicative functor and apply to itself.
The +++
on Validation
is a much simpler kind of creature—it's just the addition operation for the Semigroup
instance for the type (note that you could equivalently use Scalaz's semigroup operator |+|
here in place of +++
). You give it two Validation
results with matching semigroup types and it gives you another Validation
—not some awful ApplyOps
thing.
As a side note, in this case the addition operation for Validation
's semigroup is the same as the semigroup operation for the right side lifted into the Validation
:
scala> (xs |+| ys) == (xs |@| ys)(_ |+| _)
res3: Boolean = true
This won't always be the case, however (it's not for \/
, for example, where the semigroup accumulates errors but the applicative functor doesn't).
来源:https://stackoverflow.com/questions/17711895/scalaz-validation-with-applicative-functor-not-working