开发者

Catching an exception within a map

What is the be开发者_StackOverflow中文版st 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]).


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


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


Perhaps you want a flatMap. Here is an example, should see how it can fit :-)

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

The above would use side-effect logging (printing or putting in something mutable -- if this is done, make sure the evaluation is forced!). To get around the side-effects and caveats, the mapping function could just be of Point -> Either[CovertedPoint,Exception] and then the results can be separated with Seq.partition or similar.


Starting Scala 2.10 you could use Try to catch exceptions with flatMap to get rid of these and starting Scala 2.13 couple Try with tap to log exceptions:

List("34", "a", "1", "3", "1l")
  .flatMap(x => Try(x.toInt).tap(_.failed.foreach(println)).toOption)
// java.lang.NumberFormatException: For input string: "a"
// java.lang.NumberFormatException: For input string: "1l"
// res0: List[Int] = List(34, 1, 3)

This:

  • maps each element to an Int (our example of a code generating an exception) safe guarded by wrapping it in a Try. This will produce a Try[Int] which is either a Failure(NumberFormatException) or a Success(12).

  • These Trys are tapped to print (or log) the error for the Failures. tap applies a side effect (in this case some logging) on any value while returning the original value, so the returned value of tap is the element it's applied on, i.e. the unmodified Try.

  • We then transform the Trys into Options (Success(12) becomes Some(12) and Failure(NumberFormatException) becomes None) in order to apply a flatMap which get rids of Nones (Failures) and extract values (12 from Some(12) (Success(12))).


Before Scala 2.13, an equivalent version without tap could be:

List("34", "a", "1", "3", "1l")
  .flatMap(x =>
    (Try(x.toInt) match {
      case f @ Failure(e) => {
        println(e)
        f
      }
      case s => s
    }).toOption
  )
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜