Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catching an exception within a map

What is the best way of handling exceptions while iterating over a loop in Scala?

For instance, if I had a convert() method that could throw an exception, I'd like to catch that exception, log it, and keep iterating. Is there a "scala" way to do this?

Ideally, I'd like something like...

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

You can't do the above code since it's not a direct mapping from one list to the other (you get back Seq[Any] as opposed to Seq[ConvertedPoint]).

like image 981
shj Avatar asked Nov 03 '10 17:11

shj


People also ask

Can you handle exception in stream?

From a stream processing, we can throw a RuntimeException. It is meant to be used if there is a real problem, the stream processing is stopped; Or if we don't want to stop the whole processing, we only need to throw a caught Exception. Then it has to be handled within the stream.

How do you handle exceptions in parallel stream?

You can just throw that exception in a parallel stream the same way you do in a sequential stream, wrapping it in an unchecked exception, if it is a checked exception. If there is at least one exception thrown in a thread, the forEach invocation will propagate it (or one of them) to the caller.


2 Answers

Interesting that I had a lot of trouble explaining the advantages of using scala.util.control.Exception over try/catch, and then I start to see questions that make perfect examples out of them.

Here:

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

Your own code would look like this:

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)
)

Or, if you refactor it:

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)))
like image 162
Daniel C. Sobral Avatar answered Oct 23 '22 09:10

Daniel C. Sobral


flatMap is probably what you're looking for, but the map function has logging side-effect and these side-effects may not occur immediately if points were a view:

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")

This would print:

Conversion complete
[Error messages]
x were converted correctly

In your case, drop the view and you're probably fine. :)

To make the conversion a pure function (no side-effects), you'd probably use Either. Although I don't think it's worth the effort here (unless you actually want to do something with the errors), here's a pretty complete example of using it:

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(","))
like image 25
DaGGeRRz Avatar answered Oct 23 '22 08:10

DaGGeRRz