18

在 Scala 中迭代循环时处理异常的最佳方法是什么?

例如,如果我有一个convert()可以抛出异常的方法,我想捕获该异常,记录它并继续迭代。有没有“scala”方式来做到这一点?

理想情况下,我想要类似...

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

您不能执行上述代码,因为它不是从一个列表到另一个列表的直接映射(您返回Seq[Any]而不是Seq[ConvertedPoint])。

4

4 回答 4

17

scala.util.control.Exception有趣的是,我在解释使用over try/的优势时遇到了很多麻烦catch,然后我开始看到一些问题,这些问题可以从中做出完美的例子。

这里:

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

您自己的代码如下所示:

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

或者,如果你重构它:

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)))
于 2010-11-03T23:44:48.417 回答
16

flatMap 可能是您正在寻找的,但是 map 函数具有日志记录副作用,如果点是视图,这些副作用可能不会立即发生:

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

这将打印:

Conversion complete
[Error messages]
x were converted correctly

在您的情况下,放下视图,您可能会没事。:)

要使转换成为纯函数(没有副作用),您可能会使用 Either。虽然我认为这里不值得付出努力(除非你真的想对错误做点什么),但这里有一个非常完整的使用示例:

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(","))
于 2010-11-03T19:42:29.537 回答
6

也许你想要一个平面地图。这是一个例子,应该看看它如何适合:-)

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

以上将使用副作用日志记录(打印或放入可变的东西 - 如果这样做,请确保评估是强制的!)。为了避免副作用和警告,映射函数可以是 of Point -> Either[CovertedPoint,Exception],然后结果可以分开Seq.partition或类似。

于 2010-11-03T17:04:26.053 回答
1

开始Scala 2.10你可以使用Try捕获异常flatMap来摆脱这些并开始记录Scala 2.13异常:Trytap

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)

这个:

  • maps 每个元素到一个Int(我们生成异常的代码示例),通过将其包装在一个Try. 这将产生 aTry[Int]是 aFailure(NumberFormatException)或 a Success(12)

  • 这些Trys 被tap发送到print(或记录)Failures 的错误。tap在返回原始值时对任何值应用副作用(在本例中为一些日志记录),因此返回的值tap是它应用的元素,即未修改的Try.

  • 然后,我们将Trys 转换为Options ( Success(12)becomeSome(12)Failure(NumberFormatException)become None),以便应用flatMap去除Nones ( Failures) 并提取值 ( 12from Some(12)( Success(12))) 的 a。


之前Scala 2.13,没有的等效版本tap可能是:

List("34", "a", "1", "3", "1l")
  .flatMap(x =>
    (Try(x.toInt) match {
      case f @ Failure(e) => {
        println(e)
        f
      }
      case s => s
    }).toOption
  )
于 2019-03-17T16:05:47.963 回答