根据标题,我知道有几种合理和惯用的方法可以返回第一次成功的计算,尽管我最感兴趣的是当我们想知道最后一次尝试的具体失败时如何处理这种情况所有尝试都失败了。作为第一次尝试,我们可以使用 collectFirst
并执行以下操作:
def main(args: Array[String]) {
val xs = (1 to 5)
def check(i: Int): Try[Int] = {
println(s"checking: $i")
Try(if (i < 3) throw new RuntimeException(s"small: $i") else i)
}
val z = xs.collectFirst { i => check(i) match { case s @ Success(x) => s } }
println(s"final val: $z")
}
如果我们不关心失败,这似乎是一个合理的解决方案(实际上,因为我们总是返回成功,所以我们从不返回
Failure
,只有在没有成功计算的情况下才返回 None
)。另一方面,为了处理所有尝试都失败的情况,我们可以使用以下方法捕获最后一次失败:
def main2(args: Array[String]) {
val xs = (1 to 5)
def check(i: Int): Try[Int] = {
println(s"checking: $i")
Try(if (i < 3) throw new RuntimeException(s"small: $i") else i)
}
val empty: Try[Int] = Failure(new RuntimeException("empty"))
val z = xs.foldLeft(empty)((e, i) => e.recoverWith { case _ => check(i) })
println(s"final val: $z")
}
这里的缺点是你创建了一个“假”
Throwable
表示空,如果列表很长,我们迭代整个列表,即使我们可能很早就成功了,即使后来的迭代本质上是空操作。有没有更好的实现方式
main2
这是惯用的,不会受到上述缺点的影响?
最佳答案
你可以这样做:
@tailrec
def collectFirstOrFailure[T](l: List[T], f: T => Try[T]): Try[T] = {
l match {
case h :: Nil => f(h)
case h :: t => // f(h) orElse collectFirstOrFailure(t, f) //wish I could do this but not tailrec approved!
val res = f(h)
if (res.isFailure){
collectFirstOrFailure(t, f)
}
else {
res
}
case Nil => Failure(new RuntimeException("empty"))
}
}
val y = collectFirstOrFailure(xs.toList, check)
println(s"final val: $y")
这不是很漂亮,我们仍然需要处理空列表的情况,但我们不会创建一个新的
Failure(new RuntimeException("empty"))
每次运行(除非它是一个空列表),如果成功,我们就会停下来。我觉得 scalaz 有一些更好的方法来做到这一点,但我现在无法弄清楚。返回最后一个失败要求使这有点复杂。更新
总有
iterator
... def collectFirstOrFailureI[T](i: Iterator[T], f: T => Try[T]): Try[T] = {
while (i.hasNext){
val res = f(i.next())
if (res.isSuccess || !i.hasNext){
return res
}
}
Failure(new RuntimeException("empty"))
}
xs.toIterator
val x = collectFirstOrFailureI(xs.iterator, check)
println(s"final val: $x")
关于scala - 如何在 Scala 中最好地实现 "first success"(即,从一系列容易失败的操作中返回第一个成功),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24399126/