如何在Scala中最好地实现“第一次成功”(即,从一系列容易出错的操作中获得第一次成功)

时间:2014-06-25 02:09:08

标签: scala

根据标题,我知道有几种合理且惯用的方式可以返回第一个成功的计算,尽管我最感兴趣的是如何处理这个案例,当我们想知道具体的失败时所有尝试都失败的最后一次尝试作为第一次尝试,我们可以使用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,只返回{ {1}}如果没有成功的计算)。

另一方面,要处理所有尝试失败的情况,我们可以使用以下方法捕获上一次失败:

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并且没有上述缺点?

2 个答案:

答案 0 :(得分:2)

你可以这样做:

  @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")

答案 1 :(得分:1)

之前有一个答案:

https://stackoverflow.com/a/20665337/1296806

需要注意的是,如果所有问题都失败了,你的问题会询问最后一次失败。

我猜这就是为什么这不重复?

从答案中添加代码是微不足道的:

def bad(f: Failure) = if (count.decrementAndGet == 0) { p tryComplete new Failure(new RuntimeException("All bad", f.exception)) }

或更简单

p tryComplete f