2013-07-08 8 views
12

Come posso (meglio) convertire un'opzione restituita da una chiamata di metodo in una Prova (di preferenza, anche se un E o uno scalaz \/ o anche una Convalida potrebbe essere OK) includendo anche un valore di Errore se appropriato?Come posso (meglio) convertire un'opzione in una prova?

Per esempio, ho il seguente codice, che si sente kludgy, ma che almeno fa (la maggior parte) il lavoro:

import scala.util._ 

case class ARef(value: String) 
case class BRef(value: String) 
case class A(ref: ARef, bRef: BRef) 
class MismatchException(msg: String) extends RuntimeException(msg) 

trait MyTry { 

    // Given: 
    val validBRefs: List[BRef] 

    // Want to go from an Option[A] (obtained, eg., via a function call passing a provided ARef) 
    // to a Try[BRef], where the b-ref needs to be checked against the above list of BRefs or fail: 

    def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = { 

    val abRef = for { 
     a <- get[A](aRef) // Some function that returns an Option[A] 
     abRef = a.bRef 
     _ <- validBRefs.find(_ == abRef) 
    } yield (abRef) 

    abRef match { 
     case Some(bRef) => Success(bRef) 
     case None => Failure(new MismatchException("No B found matching A's B-ref")) 
    } 
    } 
} 

Ci si sente come se ci dovrebbe essere un modo per la finale di essere trasformato in una mappa o in una mappa piatta o in un costrutto simile e incorporato nella precedente per la comprensione.

Inoltre, preferirei essere in grado di specificare un messaggio di errore diverso se la chiamata per restituire un'opzione [A] da ARef non è riuscita (restituito None) rispetto al controllo BRef non riuscito (mi interessa solo conoscere una ragione per il fallimento, quindi una convalida scalatica non si sente come l'ideale).

È questo un luogo adatto per utilizzare un trasformatore monad? Se è così, scalaz ne fornisce uno adatto o qualcuno può dare un esempio di come sarebbe?

+0

Intendi qualcosa come 'Prova {abRef.getOrElse (lancia nuova MismatchException (" Nessun B trovato che corrisponde a A's B-ref "))}' o 'abRef.map {Success (_)} .getOrElse (Fallimento (nuova MismatchException ("Nessun B trovato corrispondente a A's B-ref"))) '? – senia

+0

@senia più quest'ultimo: 'abRef.map {Success (_)} .getOrElse (Failure (new MismatchException (" Nessun B trovato che corrisponde a A's B-ref ")))', ma sembra che dovrebbe essere possibile - e più idiomatico - per costruire questo nella comprensione per qualche motivo. – Shadowlands

+0

Come nota a margine, perché non usare 'filter' su' Option'-ie, sostituisci il tuo 'for'-comprehension con' get [A] (aRef) .map (_. BRef) .filter (validBRefs.contains) ' ? –

risposta

5

Se si inizia con un Try da ottenere andare con la for-comp allora si può eliminare la partita alla fine. È possibile farlo forzando lo Option a Try tramite fold. Ecco che cosa che potrebbe apparire come:

def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = { 
    for { 
    a <- get[A](aRef).fold[Try[A]](Failure[A](new OtherException("Invalid aRef supplied")))(Success(_)) 
    abRef = a.bRef 
    _ <- validBRefs.find(_ == abRef).fold[Try[BRef]](Failure(new MismatchException("No B found matching A's B-ref")))(Success(_)) 
    } yield abRef 
} 

Con questo approccio, è possibile ottenere diverse eccezioni per i due controlli diversi. Non è perfetto, ma forse funzionerà per te.

+0

Ah, questo è quello che mi aspettavo dovrebbe essere possibile - ha saltato il metodo fold (anche se sembra che sia arrivato solo con Scala 2.10.x). Saluti @cmbaxter! – Shadowlands

-1

ho elaborato una soluzione alternativa, anche se ancora non mi permette di specificare un messaggio di errore diverso per il caso che l'opzione [A] è None contro BREF non essere valida:

def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = 
    Try { 
    (for { 
     a <- get[A](aRef) 
     bRef = a.bRef 
     _ <- bs.find(_ == bRef) 
    } yield (bRef)) getOrElse (throw new MismatchException("No B found matching A's B-ref")) 
    } 

Immagino che mi aspetto che sia possibile avere un modo per convertire rapidamente l'Opzione restituita [A] in una Prova (in un modo Scala opportunamente idiomatico - ad esempio all'interno di una comprensione), quindi continuare l'elaborazione (ottenendo e controllando il b-ref) mentre si impostano i guasti appropriati lungo il percorso.

2

Se si desidera utilizzare un Either, è possibile utilizzare Option.toRight:

def getValidBRefForReferencedA(aRef: ARef): Either[Throwable,BRef] = {  
    for { 
    a <- get[A](aRef).toRight[Throwable](new Exception("Invalid ARef")).right 
    bRef <- validBRefs.find(_ == a.bRef).toRight(new MismatchException("No B found matching A's B-ref")).right 
    } yield bRef 
} 

Utilizzando un Try, si può semplicemente scrivere il codice in modo molto procedurale, lanciando opportuni eccezioni dove necessario e avvolgendo l'intera con Try.apply (che rileverà l'eccezione e le presenterà come istanze Failure).

def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = Try { 
    val a = get[A](aRef).getOrElse(throw new Exception("Invalid ARef")) 
    validBRefs.find(_ == a.bRef).getOrElse(throw new MismatchException("No B found matching A's B-ref")) 
} 
1

[A cura di individuare i guasti diversi]

cercato di semplificare

def getValidBRefForReferencedA(aRef: ARef): Try[BRef] = { 
    val abRef = for { 
    a <- get[A](aRef) 
    bRef = a.bRef 
    result = Either.cond(validBRefs.contains(bRef), bRef, "Invalid B Reference") 
    } yield result 

    abRef.map { 
    case Right(bRef) => Success(bRef) 
    case Left(error) => Failure(new InvalidReferenceException(error)) 
    }.getOrElse(Failure(new MismatchException("No B found matching A's B-ref"))) 
} 
Problemi correlati