2012-06-24 6 views
6

Supponiamo che io vorrebbe codificare la logica seguente a ScalaCome codificare questa logica di convalida in Scala?

 
val xdir = System.getProperty("XDir") 
if (xdir == null) 
    error("No XDir") // log the error and exit 

val ydir = System.getProperty("YDir") 
if (ydir == null) 
    error("No YDir") 

if (!new File(xdir).isDirectory) 
    error("XDir is not a directory") 

if (!new File(ydir).isDirectory) 
    error("YDir is not a directory") 

if (!new File(xdir).exists) 
    error("XDir does not exis") 

if (!new File(ydir).exists) 
    error("YDir does not exist") 
... 
(and so on) 

Qual è il modo migliore per codificare questa catena di convalide a Scala?

risposta

4

Ecco alcune cose utili:

def sysValue(prop: String) = Option(System.getProperty(prop)) //returns Option[String] 

def trySysValue(prop: String) = //returns Either[String, String] 
    sysValue(prop) map Right getOrElse Left("Absent property: " + prop) 

Poi si può usare composizione monadico Either attraverso la sua destro proiezione

val batch = //batch is Either[String, (File, File)] 
    for { 
    x <- trySysValue("XDir")).right 
    xf <- dir(x).right 
    y <- trySysValue("YDir").right 
    yf <- dir(y).right 
    } 
    yield (xf, yf) 

Dove:

def dir(s: String) = { //returns Either[String, File] 
    val f = new File(s) 
    if (!f.exists()) Left("Does not exist: " + f) 
    else if (!f.isDir()) Left("Is not a directory: " + f) 
    else Right(f) 
} 

Il lato sinistro di Either sarà un messaggio di errore. Questa composizione monadica è fail fast. È possibile ottenere una composizione che accumuli tutti i guasti (ad esempio, se non esiste né XDirYDir, si vedranno entrambi i messaggi) utilizzando scalazValidation. In tal caso, il codice sarebbe simile a questa:

def trySysValue(prop: String) = //returns Validation[String, String] 
    sysValue(prop) map Success getOrElse ("Absent property: " + prop).fail 

def dir(s: String) = { 
    val f = new File(s) 
    if (!f.exists())("Does not exist: " + f).fail 
    else if (!f.isDir()) ("Is not a directory: " + f).fail 
    else f.success 
} 

val batch = //batch is ValidationNEL[String, (File, File)] 
    (trySysValue("XDir")) flatMap dir).liftFailNel <|*|> (trySysValue("YDir")) flatMap dir).liftFailNel 
+0

Posso codificare l'accumulo degli errori senza 'scalaz'? – Michael

+0

Bene, potreste introdurre gli stessi concetti tramite la vostra libreria personale. Quello che stai cercando è una combinazione di ** funtori applicativi ** e ** monoidi **. Tuttavia, questi sono così utili e comuni, che si potrebbe anche solo usare lo scalaz. –

+0

Ne parlo qui: http://skillsmatter.com/podcast/scala/practical-scalaz-2518 –

4

qualcosa di simile:

val batch = for{ 
    a <- safe(doA, "A failed") either 
    b <- safe(doB, "B failed") either 
    c <- safe(doC, "C failed") either 
} yield(a,b,c) 
batch fold(error(_), doSuccess(_)) 

dove esegue una cassaforte, avete indovinato, cassetta di sicurezza (try/catch) operazione che richiede un fallimento (esito sinistra) un messaggio e restituisce un aut RightProjection (che ti permette di fare sopra operazione batch mentre filettatura attraverso il messaggio di errore point-of-failure)

class Catching[T](f: => T) { 
    def either(msg: String) = { 
    try { Right(f).right } catch { Left(msg).right } 
    } 
} 
def safe[T](f: => T) = new Catching(f) 

possibile aggiungere un metodo di opzione per Catching classe così, insieme con la registrazione se si desidera registrare particolari tipi di errore.

Vedere la soluzione di Jason Zaugg per right biasing Either e this thread da scala-dibattito sull'argomento pure. Ancora nessun consenso, ma la maggior parte dei "pesanti" sembra essere a favore.

Una limitazione di questo approccio è che se si tenta di aggiungere condizionali (se a = b) al blocco for {}, non verrà compilato (poiché il metodo di default Either filter restituisce Option). La soluzione è quella di implementare filtro e withFilter, tornando entrambi i casi, qualcosa che devo ancora capire/fare (se qualcuno ha già fatto, si prega di inviare)

+1

si veda anche [di convalida] (https://github.com/scalaz/scalaz/blob/master/core/src/main/scala/scalaz/Validation.scala) da scalaz. – mergeconflict

+0

@mergeconflict grazie, devo ancora tuffarmi in Scalaz, ancora bagnando i piedi in Scala. – virtualeyes