mathieu mathieu - 2 months ago 6
Scala Question

Handle exceptions in lazy view transformation

I'm playing with scala lazy views. It seems that if we have an exception during a transformation, it's not very easy to handle.
I tried to wrap with Try, but with no luck :

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


Results :

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)


What am I missing ?

Answer

You're not missing anything; that's how views are designed. They lazily call the method that throws the exception, but they simply take the result and pass that on to the method for the next map. That's too late to do anything about an exception, which bails out of the control flow before giving back a result.

If you really need to handle a situation like this, you can make things even lazier by using an iterator and manually wrapping the next call in Try. (Depending on the implementation, you might also need to wrap hasNext--for instance, a filter could throw an exception in something that is filtered out.)

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

Otherwise, if you can redesign your view to indicate failure by returning a value that indicates failure (e.g. Left(ohDear) with Right(yay) being the good value; or simply using Option), it'll work more smoothly.

If you want to keep the laziness in the w level, try implementing it like so:

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

or simply write a custom iterator that wraps calls to the dangerous iterator in Try blocks.