Обработка исключений в преобразовании ленивых представлений

Я играю с ленивыми взглядами. Кажется, что если у нас есть исключение во время преобразования, это не очень легко обработать. Я попытался завернуть с Try, но безуспешно:

var v = (1 to 10).view.map {
  case 5 => throw new Exception("foo")
  case v => v
}

val w = v.map { w => Try(w) }

w.foreach { x =>
  if (x.isFailure)
    println("got it")
  else
    println(x.get)
}

Результаты:

v: scala.collection.SeqView[Int,Seq[_]] = SeqViewM(...)

w: scala.collection.SeqView[scala.util.Try[Int],Seq[_]] = SeqViewMM(...)

1
2
3
4
java.lang.Exception: foo
    at #worksheet#.$anonfun$1.apply$mcII$sp(tt.sc0.tmp:4)
    at #worksheet#.$anonfun$1.apply(tt.sc0.tmp:3)
    at #worksheet#.$anonfun$1.apply(tt.sc0.tmp:3)
    at scala.collection.TraversableViewLike$Mapped$$anonfun$foreach$2.apply(tt.sc0.tmp:165)
    at scala.collection.Iterator$class.foreach(tt.sc0.tmp:889)
    at scala.collection.AbstractIterator.foreach(tt.sc0.tmp:1332)
    at scala.collection.IterableLike$class.foreach(tt.sc0.tmp:68)
    at scala.collection.SeqLike$$anon$2.foreach(tt.sc0.tmp:667)
    at scala.collection.TraversableViewLike$Mapped$class.foreach(tt.sc0.tmp:164)
    at scala.collection.SeqViewLike$$anon$3.foreach(tt.sc0.tmp:193)
    at scala.collection.TraversableViewLike$Mapped$class.foreach(tt.sc0.tmp:164)
    at scala.collection.SeqViewLike$$anon$3.foreach(tt.sc0.tmp:193)
    at #worksheet#.#worksheet#(tt.sc0.tmp:8)

Что мне не хватает?

2 ответа

Решение

Вы ничего не пропускаете; Вот как создаются виды. Они лениво вызывают метод, который вызывает исключение, но они просто берут результат и передают его методу для следующей карты. Слишком поздно что-либо делать с исключением, которое выходит из потока управления, прежде чем вернуть результат.

Если вам действительно нужно справиться с такой ситуацией, вы можете сделать вещи еще более ленивыми, используя итератор и вручную оборачивая next вызывать Try, (В зависимости от реализации, вам также может понадобиться hasNext- Например, фильтр может выдать исключение во что-то, что отфильтровано.)

val wb = Seq.newBuilder[Try[Int]]
val vi = v.iterator
while (vi.hasNext) wb += Try{ vi.next }
val w = wb.result

В противном случае, если вы можете изменить вид, указав сбой, вернув значение, указывающее сбой (например, Left(ohDear) с Right(yay) быть хорошей ценностью; или просто используя Option), это будет работать более плавно.

Если вы хотите сохранить свою лень в w уровень, попробуйте реализовать это так:

val vi = v.iterator
val w = Iterator.continually(Try(vi.next)).takeWhile(_ => vi.hasNext)

или просто написать собственный итератор, который оборачивает вызовы опасного итератора в Try блоки.

С ленивыми представлениями дело в том, что лежащий в основе итератор не вычисляется до consumer потребляет это. А также map, flatMap и т. д. не являются потребителями, они transformer которые превращают lazy view в другой lazy view,

Потребители включают foreach, fold и т. д. Только когда потребители потребляют представление, фактические шаги преобразования будут выполнены. Так что вы просто должны обернуть свой потребительский вызов в Try

val v = (1 to 10).view.map {
  case 5 => throw new Exception("foo")
  case v => v
}

val w = v.map(_ + 10)

Try(w.foreach(i =>
  println(i)
)) match {
  case Success(_) => println("Successfully done.")
  case Failure(ex) => println("I tried so hard... but it doesn't even matter")
}

И... вещь с Try в том, что вы должны обернуть создателей проблемы этим, чтобы он защищал любые возможные исключения. Так что вы должны сделать, это обернуть ваше преобразование с Try,

val view1 = (1 to 10).view

val view2 = view1.map(v => Try(v match {
  case 5 => throw new Exception("foo")
  case v => v
}))

// Now, your view2 is actually a view of Try's

// you can map it to transform again

val view3 = view2.map({
  case Success(v) => v + 20
  case Failure(ex) => throw ex
})

// now consume

view3.foreach({
  case Success(v) => println(v)
  case Failure(ex) => println("this one is a bad boy")
})
Другие вопросы по тегам