处理惰性视图转换中的异常

Handle exceptions in lazy view transformation

我正在玩 scala 惰性视图。看来如果我们在转换的过程中出现了异常,就不太好处理了。 我尝试用 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.apply$mcII$sp(tt.sc0.tmp:4)
    at #worksheet#.$anonfun.apply(tt.sc0.tmp:3)
    at #worksheet#.$anonfun.apply(tt.sc0.tmp:3)
    at scala.collection.TraversableViewLike$Mapped$$anonfun$foreach.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.foreach(tt.sc0.tmp:667)
    at scala.collection.TraversableViewLike$Mapped$class.foreach(tt.sc0.tmp:164)
    at scala.collection.SeqViewLike$$anon.foreach(tt.sc0.tmp:193)
    at scala.collection.TraversableViewLike$Mapped$class.foreach(tt.sc0.tmp:164)
    at scala.collection.SeqViewLike$$anon.foreach(tt.sc0.tmp:193)
    at #worksheet#.#worksheet#(tt.sc0.tmp:8)

我错过了什么?

惰性视图的问题是,在 consumer 使用它之前,不会评估底层迭代器。而 mapflatMap 等不是消费者,它们是 transformer,将一个 lazy view 转换为另一个 lazy view

消费者包括foreachfold等。只有当消费者消费视图时,才会执行实际的转换步骤。所以你只需要将你的消费调用包装在 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")
})

你没有遗漏任何东西;这就是视图的设计方式。他们懒惰地调用抛出异常的方法,但他们只是 获取结果 并将其传递给下一个地图的方法。对异常做任何事情都为时已晚,异常会在返回结果之前退出控制流。

如果您真的需要处理这样的情况,您可以通过使用迭代器并手动将 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 块中。