Сопоставьте и уменьшите/сверните HList scalaz.Validation

Я начал с чего-то вроде этого:

def nonEmpty[A] = (msg: String) => (a: Option[A]) => a.toSuccess(msg)

val postal: Option[String] = request.param("postal")
val country: Option[String] = request.param("country")

val params =
  (postal  |> nonEmpty[String]("no postal" )).toValidationNel |@|
  (country |> nonEmpty[String]("no country")).toValidationNel

params { (postal, country) => ... }

Теперь я подумал, что было бы неплохо уменьшить шаблон для лучшей читабельности и чтобы не объяснять младшим членам команды, что означают .toValidateNel и |@|. Первой мыслью было List, но потом последняя строка перестала работать, и мне пришлось отказаться от некоторой статической безопасности. Поэтому я посмотрел в сторону Shapeless:

import shapeless._; import poly._; import syntax.std.tuple._

val params = (
  postal  |> nonEmpty[String]("no postal"),
  country |> nonEmpty[String]("no country")
)

params.map(_.toValidatioNel).reduce(_ |@| _)

однако я даже не могу пройти мимо бита .map(...). Я пробовал в соответствии с предложением #scalaz:

type Va[+A] = Validation[String, A]
type VaNel[+A] = ValidationNel[String, A]

params.map(new (Va ~> VaNel) { def apply[T](x: Va[T]) = x.toValidationNel })

...но безрезультатно.

Я обратился за помощью к #scalaz, но, похоже, у людей нет готового ответа. Тем не менее, я действительно заинтересован в том, чтобы узнать, как решить эту проблему как в практических, так и в учебных целях.

P.S. на самом деле мои проверки заключены в Kleisli[Va, A, B], чтобы я мог составлять отдельные шаги проверки, используя >=>, но это кажется ортогональным к проблеме, поскольку к тому времени, когда будет достигнуто .map(...), все Kleislis будут "сокращены" до Validation[String, A].


person Erik Kaplun    schedule 16.10.2014    source источник
comment
Вам определенно нужно определить Poly1 как объект (по разным дурацким причинам, связанным со Scala, связанным со стабильными идентификаторами). Это очень похоже на обход, и traverse от shapeless-contrib позволит вам выполнить toValidationNel и (моральный эквивалент) reduce(_ |@| _) за один шаг.   -  person Travis Brown    schedule 16.10.2014
comment
См. также мою смутно связанную запись в блоге здесь.   -  person Travis Brown    schedule 16.10.2014


Ответы (1)


Вот как это будет выглядеть с traverse shapeless-contrib:

import scalaz._, Scalaz._
import shapeless._, contrib.scalaz._, syntax.std.tuple._

def nonEmpty[A] = (msg: String) => (a: Option[A]) => a.toSuccess(msg)

val postal: Option[String] = Some("00000")
val country: Option[String] = Some("us")

val params = (
  postal  |> nonEmpty[String]("no postal"),
  country |> nonEmpty[String]("no country")
)

А потом:

object ToVNS extends Poly1 {
  implicit def validation[T] = at[Validation[String, T]](_.toValidationNel)
}

val result = traverse(params.productElements)(ToVNS).map(_.tupled)

Теперь result — это ValidationNel[String, (String, String)], и вы можете делать с ним все, что вы могли бы сделать с ужасной вещью ApplicativeBuilder, которую вы получите, сократив ее с помощью |@|.

person Travis Brown    schedule 16.10.2014
comment
Не могли бы вы немного пояснить, почему и чем traverse отличается от обычного map, почему он работает, а map нет, и почему он (пока) не является частью Shapeless? - person Erik Kaplun; 17.10.2014
comment
@TravisBrown Зачем нам нужны .productElements и .map(_.tupled)? Разве Generic поддержка кортежей shapeless не должна позволять нам traverse напрямую обращаться к кортежу? @ErikAllik traverse похож на map, который накапливает Applicative эффектов. Если бы мы использовали обычные map, мы бы получили (ValidationNel[String, String], ValidationNel[String, String]); traverse также упорядочивает эффект ValidationNel, поэтому у нас есть один ValidationNel для всего кортежа. traverse зависит от scalaz, поэтому он не может быть частью основного Shapeless, так как они не хотят вводить эту зависимость. - person lmm; 17.10.2014
comment
Трэвис: Буду очень признателен за любые указания на то, почему ApplicativeBuilder ужасен (например, а не просто ValidationNel)! - person Erik Kaplun; 17.10.2014
comment
@Erik: проблема с ApplicativeBuilder в том, что это просто синтаксический хак - это ничего не значит. По сути, это список параметров, ожидающий функции, которую он может поднять в аппликативный функтор и применить к себе. - person Travis Brown; 17.10.2014
comment
Так это плохо, потому что синтаксические хаки вообще плохи, или просто этот хак плохой? Или просто ненужный? - person Erik Kaplun; 17.10.2014
comment
Я предполагаю, что ужасно - это немного сильно, но в отличие от Haskell, где вы просто пишете foo <$> a <*> b <*> c (функции и аргументы в обычном порядке, никаких промежуточных Builder вещей и т. д.). - person Travis Brown; 17.10.2014