Поймать исключение на карте - PullRequest
16 голосов
/ 03 ноября 2010

Каков наилучший способ обработки исключений при итерации по циклу в 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 ]

16 голосов
/ 03 ноября 2010

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(","))
15 голосов
/ 04 ноября 2010

Интересно, что у меня было много проблем с объяснением преимуществ использования 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)))
6 голосов
/ 03 ноября 2010

Возможно, вы хотите flatMap.Вот пример, должен увидеть, как он может соответствовать: -)

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

Выше было бы использовать ведение журнала побочных эффектов (печать или вставка чего-то изменяемого - если это сделано, убедитесь, что оценкавынужден!).Чтобы обойти побочные эффекты и предостережения, функция отображения может иметь значение Point -> Either[CovertedPoint,Exception], а затем результаты могут быть разделены с помощью Seq.partition или аналогичным.

0 голосов
/ 17 марта 2019

Начиная с Scala 2.10, вы можете использовать Try, чтобы перехватывать исключения с помощью flatMap, чтобы избавиться от них, и начинать Scala 2.13 пару Try с tap для регистрации исключений:

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)

Это:

  • map s каждый элемент в Int (наш пример кода, генерирующего исключение) защищен, заключая его в Try. Это даст Try[Int], который будет Failure(NumberFormatException) или Success(12).

  • Эти Try с tap показывают print (или регистрируют) ошибку для Failure с. tap применяет побочный эффект (в данном случае некоторую регистрацию) к любому значению при возврате исходного значения, поэтому возвращаемое значение tap является элементом, к которому оно применено, т.е. неизмененным Try .

  • Затем мы преобразуем Try s в Option s (Success(12) становится Some(12) и Failure(NumberFormatException) становится None), чтобы применить flatMap которые получают число None с (Failure с) и извлекают значения (12 из 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
  )
...