2014-10-16 19 views
7

Ho iniziato con qualcosa di simile:Mappa e ridurre/fold over HList di 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) => ... } 

Ora ho pensato che sarebbe stato bello per ridurre il boilerplate per una migliore leggibilità e per non dover spiegare a più membri della squadra junior cosa significa .toValidateNel e |@|. Il primo pensiero è stato List ma poi l'ultima riga avrebbe smesso di funzionare e avrei dovuto rinunciare alla sicurezza statica. Così ho guardato verso informe:

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

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

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

tuttavia, non riesco nemmeno a superare il bit .map(...). Ho provato come suggerimento su #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 }) 

... inutilmente.

Ho chiesto aiuto su #scalaz ma non sembra che la gente abbia una risposta immediata. Tuttavia, sono davvero entusiasta di imparare come risolvere questo problema sia a livello pratico che di apprendimento.

P.S. in realtà le mie convalide sono racchiuse all'interno di in modo da poter comporre singole fasi di convalida utilizzando >=> ma che sembra essere ortogonale al problema in quanto dal momento in cui è stato raggiunto .map(...), tutti gli Kleisli s saranno stati "ridotti" a Validation[String, A].

+1

Avrete sicuramente bisogno di definire il 'Poly1' come un oggetto (per varie strane ragioni correlate a Scala che hanno a che fare con identificatori stabili). Questo assomiglia molto ad un traversal, e la 'traverse' di shapeless-contrib ti permetterebbe di fare il' toValidationNel' e (l'equivalente morale di) 'riduci (_ | @ | _)' in un solo passaggio. –

+1

Vedere anche il mio blog post vagamente correlato [qui] (http://meta.plasm.us/posts/2013/06/05/applicative-validation-syntax/). –

risposta

3

Qui è ciò che questo sarebbe simile con 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") 
) 

E poi:

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

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

Ora result è un ValidationNel[String, (String, String)], e si può fare qualsiasi cosa con esso che si potrebbe fare con la cosa terribile ApplicativeBuilder da ridurre con |@|.

+0

potresti approfondire un po 'sul perché e come 'traverse' è diverso da una normale' mappa', perché funziona e 'map' non lo fa, e perché non è (ancora) parte di Shapeless? –

+2

@TravisBrown Perché abbiamo bisogno di '.productElements' e' .map (_. Tupled) '? Il supporto "generico" di shapeless non dovrebbe permetterci di "attraversare" direttamente la tupla? @ErikAllik 'traverse' è come una' mappa' che accumula effetti 'Applicativi'. Se dovessimo usare 'map' normale otterremmo un' (ValidationNel [String, String], ValidationNel [String, String]) '; il 'traverse' anche" sequenze "l'effetto' ValidationNel' quindi abbiamo un singolo 'ValidationNel' per la tupla generale. 'traverse' dipende da scalaz, quindi non può far parte del principale Shapeless in quanto non vuole introdurre tale dipendenza. – lmm

+0

@lmm: perfetto, grazie! –