Валидация Scalaz с аппликативным функтором |@| не работает

Я пытаюсь использовать проверку Scalaz 7 в своем приложении. Однако у меня возникла проблема с получением аппликативного функтора |@| для объединения моих ошибок. Вот код, который у меня есть:

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])(_ |@| _)
}

Функции minValidate и sizeValidate просто возвращают ValidationResults.

Проблема в том, что этот код не компилируется. Сообщение об ошибке:

Type mismatch, expected F0.type#M[NotInferedB], actual: ValidationResult

Я понятия не имею, что это значит... мне нужно предоставить Scala больше информации о типах?

То, что я пытаюсь сделать, это если все поля равны successNels, то верните это, в противном случае верните комбинацию всех failureNels.

Изменился ли |@| по сравнению с предыдущей версией Scalaz? Потому что даже если я сделаю что-то вроде:

().successNel |@| ().successNel

Я получаю ту же ошибку.

Обновить

Я начал копаться в исходниках Scalaz и нашел +++, который, похоже, делает то, что мне нужно.

В чем разница между +++ и |@|?


person cdmckay    schedule 17.07.2013    source источник


Ответы (1)


Синтаксис аппликативного построителя Scalaz (|@|) дает вам способ «поднять» функции в аппликативный функтор. Предположим, у нас есть следующие результаты, например:

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

Мы можем поднять функцию объединения списков (++) в Validation следующим образом:

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

Этот синтаксис немного странный — он очень отличается от того, как вы переносите функции в аппликативный функтор, например, в Haskell, и разработан таким образом в первую очередь для того, чтобы перехитрить довольно глупую систему вывода типов Scala. См. мой ответ здесь или пост в блоге для дальнейшего обсуждения.

Одна часть странности заключается в том, что xs |@| ys на самом деле ничего не означает само по себе — это, по сути, список аргументов, ожидающих применения к функции, которую она поднимет в свой аппликативный функтор и применит к себе.

+++ на Validation — это гораздо более простой вид существа — это просто операция сложения для экземпляра Semigroup для типа (обратите внимание, что вы могли бы эквивалентно использовать полугрупповой оператор Scalaz |+| вместо +++). Вы даете ему два результата Validation с соответствующими типами полугрупп, и он дает вам еще один результат Validation, а не какие-то ужасные ApplyOps вещи.


В качестве примечания, в этом случае операция сложения для полугруппы Validation такая же, как операция полугруппы для правой части, поднятой в Validation:

scala> (xs |+| ys) == (xs |@| ys)(_ |+| _)
res3: Boolean = true

Однако так будет не всегда (например, не для \/, где полугруппа накапливает ошибки, а аппликативный функтор — нет).

person Travis Brown    schedule 18.07.2013