2010-11-03 11 views
12

Qual è il modo migliore di gestire le eccezioni mentre si scorre su un ciclo in Scala? Ad esempio, se avessi un metodo convert() che potrebbe generare un'eccezione, vorrei rilevare quell'eccezione, registrarla e continuare a ripetere. Esiste un modo "scala" per farlo?Scala: rilevazione di un'eccezione all'interno di una mappa

Idealmente, vorrei qualcosa di simile ...

val points: Seq[Point] = ... 
val convertedPoints: Seq[ConvertedPoint] = points.map(
    p => { 
    try { p.convert() } 
    catch { case ex: Exception => logger.error("Could not convert", ex) } 
}) 

Non si può fare il codice di cui sopra in quanto non è una mappatura diretta da un elenco all'altro (si torna Seq [Qualsiasi] al contrario di Seq [ConvertedPoint]). Qualsiasi aiuto sarebbe molto apprezzato!

Grazie!

risposta

5

Forse vuoi un flatMap. Ecco un esempio, dovrebbe vedere come si può andare bene :-)

List(1,2,3,4).flatMap(x => if (x > 2) Some(x) else None) 

È possibile che userebbe effetto collaterale di registrazione (la stampa o la messa in qualcosa di mutevole - se questo è fatto, assicurarsi che la valutazione è costretto!). Per aggirare gli effetti collaterali e gli avvertimenti, la funzione di mappatura potrebbe essere solo di Point -> Either[CovertedPoint,Exception] e quindi i risultati possono essere separati con Seq.partition o simili.

14

flatMap è probabilmente quello che stai cercando, ma la funzione di mappa ha la registrazione effetto collaterale e questi effetti collaterali non si può verificare immediatamente se i punti sono stati una vista:

val convertedPoints = points.view.flatMap { p => 
    try { 
    Some(p.convert) 
    } catch { 
    case e : Exception => 
    // Log error 
    None 
    } 
} 
println("Conversion complete") 
println(convertedPoints.size + " were converted correctly") 

Questo sarebbe stampare:

Conversion complete 
[Error messages] 
x were converted correctly 

Nel tuo caso, lascia cadere la vista e probabilmente stai bene. :)

Per rendere la conversione una funzione pura (senza effetti collaterali), probabilmente userete Either. Anche se io non credo che sia valsa la pena qui (a meno che non si vuole realmente fare qualcosa con gli errori), ecco un esempio abbastanza completa di usarlo:

case class Point(x: Double, y: Double) { 
    def convert = { 
    if (x == 1.0) throw new ConversionException(this, "x is 1.0. BAD!") 
    else ConvertedPoint(x, y) 
    } 
} 
case class ConvertedPoint(x: Double, y: Double) 
class ConversionException(p: Point, msg: String) extends Exception(msg: String) 


val points = List(Point(0,0), Point(1, 0), Point(2,0)) 

val results = points.map { p => 
    try { 
    Left(p.convert) 
    } catch { 
    case e : ConversionException => Right(e) 
    } 
} 

val (convertedPoints, errors) = results.partition { _.isLeft } 

println("Converted points: " + convertedPoints.map(_.left.get).mkString(",")) 
println("Failed points: " + errors.map(_.right.get).mkString(",")) 
+0

hai rubato la mia risposta, ma +1 per spiegandolo meglio :-) Benvenuto in SO. –

+1

Concordato: la tua risposta era corretta al 100% (e un'ispirazione), ma penso che aggiungere ulteriori dettagli giustificasse un'altra risposta invece di un semplice commento. :) – DaGGeRRz

12

interessante che ho avuto un sacco di difficoltà a spiegare il vantaggi dell'uso di scala.util.control.Exception su try/catch, e quindi comincio a vedere domande che ne fanno esempi perfetti.

Qui:

import scala.util.control.Exception._ 
List(1, 23, 5, 2, 0, 3, 2) flatMap (x => catching(classOf[Exception]) opt (10/x)) 

il proprio codice sarebbe simile a questa:

val points: Seq[Point] = ... 
val convertedPoints: Seq[ConvertedPoint] = points.flatMap(
    p => handling(classOf[Exception]) by { ex => 
    logger.error("Could not convert", ex); None 
    } apply Some(p.convert) 
) 

O, se si refactoring:

val exceptionLogger = handling(classOf[Exception]) by { ex => 
    logger.error("Could not convert", ex); None 
} 
val convertedPoints: Seq[ConvertedPoint] = points.flatMap(p => exceptionLogger(Some(p.convert)))