2014-10-16 12 views
7

Zacząłem z czymś takim:Mapa i zmniejszyć/zagiąć HList z 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) => ... } 

Teraz pomyślałem, że byłoby miło, aby zmniejszyć boilerplate dla lepszej czytelności i nie trzeba wyjaśniać więcej młodszych członków zespołu, co oznacza .toValidateNel i |@|. Pierwsza myśl to List, ale ostatnia linia przestałaby działać i musiałbym zrezygnować z pewnego statycznego bezpieczeństwa. Więc spojrzał na bezkształtne:

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

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

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

jednak, że nie może nawet wydawać się ominąć .map(...) bit. Próbowałem zgodnie z sugestią na temat #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 }) 

... bez skutku.

Poprosiłem o pomoc w sprawie #scalaz, ale nie wydaje się, że ludzie mają po prostu nieoczekiwane odpowiedzi. Jednak bardzo chciałbym dowiedzieć się, jak rozwiązać ten problem zarówno w celach praktycznych, jak i edukacyjnych.

P.S. w rzeczywistości moje sprawdzenia poprawności są zawijane wewnątrz Kleisli[Va, A, B], dzięki czemu mogłem komponować poszczególne kroki sprawdzania poprawności przy użyciu >=>, ale wydaje się, że jest to ortogonalne względem czasu, w którym osiągnięto .map(...), a wszystkie Kleisli s zostaną "zredukowane" do Validation[String, A].

+1

Zdecydowanie trzeba będzie zdefiniować 'Poly1' jako obiekt (z różnych zwariowanych powodów związanych ze Scala związanych ze stabilnymi identyfikatorami). To wygląda trochę jak traversal, a "bezkształtne" contrib "pozwala ci wykonać' toValidationNel' i (moralny odpowiednik) 'reduce (_ | @ | _)' w jednym kroku. –

+1

Zobacz także mój mało znany post na blogu [tutaj] (http://meta.plasm.us/posts/2013/06/05/applicative-validation-syntax/). –

Odpowiedz

3

Oto, co to będzie wyglądać z shapeless-contrib „s traverse:

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

, a następnie:

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

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

Teraz result jest ValidationNel[String, (String, String)] i można zrobić coś z tym, że można rób z okropnym ApplicativeBuilder rzeczą, którą otrzymasz od redukcji z |@|.

+0

Czy mógłbyś trochę wyjaśnić, dlaczego i jak "traverse" różni się od zwykłej 'mapy ', dlaczego działa, a' map' nie, i dlaczego nie jest (jeszcze) częścią Shapeless? –

+2

@TravisBrown Dlaczego potrzebujemy '.productElements' i' .map (_. Tupled) '? Czy obsługa "krotki" nie powinna być "bezkształtna", abyśmy mogli "przetrawić" krotkę bezpośrednio? @ErikAllik 'trawers' jest jak' mapa', która kumuluje efekty 'Applicative'. Gdybyśmy używali zwykłej 'map', otrzymalibyśmy' (ValidationNel [String, String], ValidationNel [String, String]); 'traverse' również" sekwencje "efektu' ValidationNel', więc mamy pojedynczy 'ValidationNel' dla ogólnej krotki. 'trawers" zależy od skalaza, więc nie może być częścią głównego Bezkształtnego, ponieważ nie chcą wprowadzić tej zależności. – lmm

+0

@lmm: idealnie, dziękuję! –