2015-09-02 18 views
6

Sfondo

Ho Map[String,String] dei valori di configurazione. Voglio estrarre una serie di chiavi e fornire messaggi di errore significativi se qualcuno di essi manca. Per esempio:Utilizzo della convalida con | @ | in Scalaz

val a = Map("url"->"http://example.com", "user"->"bob", "password"->"12345") 

dire che voglio trasformare questo in una classe case:

case class HttpConnectionParams(url:String, user:String, password: String) 

Ora, posso semplicemente utilizzare un ciclo for per estrarre i valori:

for(url <- a.get("url"); 
    user <- a.get("user"); 
    password <- a.get("password")) yield { 
    HttpConnectionParams(url,user,password) 
} 

Per prendi un Option[HttpConnectionParams]. Questo è bello e pulito, tranne se ottengo un None quindi non so cosa mancasse. Mi piacerebbe fornire queste informazioni.

Validazione con Scalaz

Immettere scalaz. Sto usando la versione 7.1.3.

Da quello che sono riuscito a mettere insieme (un buon riferimento è here) posso usare disgiunzioni:

for(url <- a.get("url") \/> "Url must be supplied"; 
    user <- a.get("user") \/> "Username must be supplied"; 
    password <- a.get("password") \/> "Password must be supplied") yield { 
    HttpConnectionParams(url,user,password) 
} 

Questo è bello perché ora ho un messaggio di errore, ma questo è perché railway oriented si ferma al primo fallimento. Cosa succede se voglio ottenere tutti gli errori? Usiamo la convalida e il costruttore applicativa (alias "| @ |"):

val result = a.get("url").toSuccess("Url must be supplied") |@| 
      a.get("username").toSuccess("Username must be supplied") |@| 
      a.get("password").toSuccess("Password must be supplied") 

result.tupled match { 
    case Success((url,user,password)) => HttpConnectionParams(url,user,password) 
    case Failure(m) => println("There was a failure"+m) 
} 

Domande

Questo fa quello che mi aspetto, ma ho alcune domande circa l'utilizzo:

  • Is c'è un'alternativa facile da usare a scalaz per questo caso d'uso? Preferirei non aprire la scatola di Pandora e introdurre lo scalaz se non dovessi farlo.
  • Una ragione per cui non vorrei usare lo scalaz è che è davvero molto difficile capire cosa fare se non si conosce l'intero framework come me. Ad esempio, qual è l'elenco di impliciti di cui hai bisogno per far funzionare il codice sopra riportato? import scalaz._ in qualche modo non ha funzionato per me. [1] Come posso capire questo dai documenti API?
  • Esiste un modo più sintetico per esprimere il caso di utilizzo della convalida? Sono inciampato fino a quando non sono arrivato a qualcosa che funzionava e non ho idea se ci sono altri modi migliori di fare la stessa cosa in scalaz.

[1] Dopo molta costernazione sono arrivato a questo insieme di importazioni per il caso di utilizzo applicativo. Speriamo che questo aiuta qualcuno:

import scalaz.std.string._ 
import scalaz.syntax.std.option._ 
import scalaz.syntax.apply._ 
import scalaz.Success 
import scalaz.Failure 
+0

"Una ragione per cui non vorrei usare lo scalaz è che è davvero molto difficile capire cosa fare se non, come me, conosca l'intero framework" - Non dirlo direttamente a Tony Morris;). Scalaz potrebbe non essere la tazza di tè per tutti; c'è una ripida curva di apprendimento coinvolta per capirlo se non sei della persuasione hard FP e/o non hai familiarità con Haskell (ci vuole molta ispirazione da quella lingua). È una libreria potente se sai cosa farne - ho l'impressione che tu la stia battendo perché non puoi preoccuparti di impararla a fondo - non è colpa di Scalaz –

+0

Un'alternativa a Scalaz è Cats. Uno degli obiettivi è di essere più aperto e user friendly rispetto a Scalaz in termini di documentazione ed esempi, anche se ho trovato la comunità Scalaz pronta ad aiutare con qualsiasi problema che ho avuto. –

+0

Sì, un collaboratore ha consigliato i gatti ma alla versione 0.2 è pronto per essere usato da tutti? –

risposta

13

Si può fare questo un po 'più ben definendo un metodo di supporto e saltando il passo .tupled utilizzando .apply:

import scalaz._, Scalaz._ 

def lookup[K, V](m: Map[K, V], k: K, message: String): ValidationNel[String, V] = 
    m.get(k).toSuccess(NonEmptyList(message)) 

val validated: ValidationNel[String, HttpConnectionParams] = (
    lookup(a, "url", "Url must be supplied") |@| 
    lookup(a, "username", "Username must be supplied") |@| 
    lookup(a, "password", "Password must be supplied") 
)(HttpConnectionParams.apply) 

Inoltre, si prega di non vergognarsi di utilizzare import scalaz._, Scalaz._. Lo facciamo tutti e va bene nella stragrande maggioranza dei casi. Puoi sempre tornare indietro e perfezionare le tue importazioni in un secondo momento.Sono ancora in attesa dello this answer che scrissi anni fa: non dovresti sentirti in dovere di avere una comprensione completa di Scalaz (o gatti) per poterne usare efficacemente pezzi.

+2

Appena notato che questa non è davvero una risposta alla tua prima domanda, ma per ora non esiste un pregiudizio di destra, accumulando il tipo di disgiunzione nella libreria standard, quindi, a meno che non si desideri eseguire il rollover, i gatti o Scalaz (o Scalactic) sono la soluzione migliore. –

+0

E hai risposto a una domanda che avevo senza che me lo chiedessi, che è l'uso delle classi di liste non vuote (ValidationNel, ecc.). Questo per evitare una semplice concatenazione di più messaggi di errore per chiunque segua. Grazie, questo è stato molto utile. –

Problemi correlati