Scalaz Validation with applicative functor |@| not

2019-02-09 20:20发布

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 successNels, then return that, otherwise, return a combination of all the failureNels.

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条回答
看我几分像从前
2楼-- · 2019-02-09 21:02

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

查看更多
登录 后发表回答