来自finally块的Scala尾递归
Scala tail recursion from finally block
我想将函数 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 _ =>
}
}
但是,上面的代码无法编译——抱怨这个函数不是尾递归的。为什么不呢?
不确定该方法的意图,但您可以这样做:
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
}
}
此解决方案遍历所有元素并生成(抛出)最后一个错误(如果有):
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)
}
我知道这样使用@annotation.tailrec
来自这里:
def fac(n:Int):Int = if (n<=1) 1 else n*fac(n-1)
你应该有这个:
@scala.annotation.tailrec
def facIter(f:Int, n:Int):Int = if (n<2) f else facIter(n*f, n-1)
def fac(n:Int) = facIter(1,n)
如@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
的 return 类型更改为 List[Try[Unit]]
(或简单地 Try[Unit]
如果你修改代码只取最后一个)。方法的 return 类型最好描述其实际执行的部分操作——可能 returning 错误。
我想将函数 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 _ =>
}
}
但是,上面的代码无法编译——抱怨这个函数不是尾递归的。为什么不呢?
不确定该方法的意图,但您可以这样做:
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
}
}
此解决方案遍历所有元素并生成(抛出)最后一个错误(如果有):
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)
}
我知道这样使用@annotation.tailrec
来自这里:
def fac(n:Int):Int = if (n<=1) 1 else n*fac(n-1)
你应该有这个:
@scala.annotation.tailrec
def facIter(f:Int, n:Int):Int = if (n<2) f else facIter(n*f, n-1)
def fac(n:Int) = facIter(1,n)
如@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
的 return 类型更改为 List[Try[Unit]]
(或简单地 Try[Unit]
如果你修改代码只取最后一个)。方法的 return 类型最好描述其实际执行的部分操作——可能 returning 错误。