Обработка исключений в преобразовании ленивых представлений
Я играю с ленивыми взглядами. Кажется, что если у нас есть исключение во время преобразования, это не очень легко обработать. Я попытался завернуть с 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")
})