来自finally块的Scala尾递归

时间:2017-02-14 15:58:57

标签: scala recursion tail-recursion try-catch-finally

我想将函数f应用于List的每个元素,而不是在第一个错误处停止,而是仅抛出最后一个错误(如果有):

@annotation.tailrec
def tryAll[A](xs: List[A])(f: A => Unit): Unit = {
  xs match {
    case x :: xt =>
      try {
        f(x)
      } finally {
        tryAll(xt)(f)
      }
    case _ =>
  }
}

但是,上面的代码没有编译 - 它抱怨这个函数不是尾递归的。为什么不呢?

4 个答案:

答案 0 :(得分:1)

此解决方案遍历所有元素并生成(抛出)最后一个错误(如果有):

def tryAll[A](xs: List[A])(f: A => Unit): Unit = {
  val res = xs.foldLeft(Option.empty[Throwable]) {
    case (maybeThrowable, a) =>
      Try(f(a)) match {
        case Success(_) => maybeThrowable
        case Failure(e) => Option(e)
      }
  }

  res.foreach(throwable => throw throwable)
}

答案 1 :(得分:0)

如@HristoIliev所述,您的方法不能是尾递归,因为finally调用不能保证是尾调用。这意味着以这种方式使用try的任何方法都不会是尾递归的。另见this answer

再次调用该方法是一种反复尝试的方法,直到它成功为止,因为在每个阶段它都会抛出异常,你可能不会处理。相反,我认为使用Try的功能方法,从视图中获取失败直到操作成功。这种方法的唯一缺点是它不会为你提供任何例外处理(这也是一个优势!)。

def tryAll[A](xs: List[A])(f: A => Unit): Unit =
    xs.view.map(x => Try(f(x))).takeWhile(_.isFailure).force


scala> val list = List(0, 0, 0, 4, 5, 0)

scala> tryAll(list)(a => println(10 / a))
2

如果您真的想要处理异常(或只是最后一个异常),您可以将tryAll的返回类型更改为List[Try[Unit]](如果修改代码,则只需更改Try[Unit]只采取最后一个)。方法的返回类型更好地描述了它实际做的部分 - 可能会返回错误。

答案 2 :(得分:-1)

不确定方法的用意,但你可以这样:

  final def tryAll[A](xs: List[A])(f: A => Unit): Unit = {
      xs match {
        case x :: xt =>
          try {
            f(x)
          } catch {
            case e => tryAll(xt)(f)
          }
        case _ => //do something else
      }
    }

答案 3 :(得分:-1)

我知道这种方式使用@ annotation.tailrec

由此:

.../displayname/@Value

你应该有这个:

def fac(n:Int):Int = if (n<=1) 1 else n*fac(n-1)