【问题标题】:Catching an exception within a map在地图中捕获异常
【发布时间】:2011-05-04 14:57:27
【问题描述】:

在 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])。

【问题讨论】:

    标签: scala loops dictionary exception scala-collections


    【解决方案1】:

    有趣的是,我在解释使用 scala.util.control.Exception 优于 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)))
    

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 你偷了我的答案,但 +1 是为了更好地解释它:-) 欢迎来到 SO。
      • 同意 - 你的答案是 100% 正确的(也是一个灵感),但我认为添加更多细节可以证明另一个答案,而不仅仅是评论。 :)
      【解决方案3】:

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

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

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

      【讨论】:

        【解决方案4】:

        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 中得到安全保护。这将产生一个Try[Int],它可以是Failure(NumberFormatException)Success(12)

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

        • 然后我们将Trys 转换为Options(Success(12) 变为Some(12)Failure(NumberFormatException) 变为None)以应用flatMap 从而摆脱@987654354 @s (Failures) 并提取值(12 from Some(12) (Success(12)))。


        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
          )
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-03
          • 2012-07-10
          • 1970-01-01
          • 2011-05-29
          • 2011-07-02
          相关资源
          最近更新 更多