在recent StackOverflow answer中,我提供了以下递归代码:
def retry[T](n: Int)(fn: => T): T = {
try {
fn
} catch {
case e if n > 1 =>
retry(n - 1)(fn)
}
}
如果我添加@tailrec
注释,我会得到:
无法优化@tailrec带注释的方法重试:它包含一个 递归呼叫不在尾部位置。
我能够破解尾部递归替代方案,但我仍然想知道为什么这没有优化。为什么不呢?
答案 0 :(得分:10)
要进行尾递归优化,必须将其转换为以下内容:
def retry[T](n: Int)(fn: => T): T = {
START:
try {
fn
} catch {
case e if n > 1 =>
n = n - 1
GOTO START
}
}
当它执行GOTO
循环时,它必须离开catch
块的范围。但在原始递归版本中,递归调用的执行仍在catch
块内。如果语言允许这可能会改变代码的含义,那么这将不是有效的优化。
编辑:在评论中与Rex Kerr的讨论中,这是Scala中的行为保留转换(但仅在没有finally
时)。显然,只是Scala编译器还没有认识到catch
块的最后一次调用没有finally
处于尾部调用位置。
答案 1 :(得分:7)
我不认为以尾递归形式放置代码的已实现转换列表包括遍历异常处理块。这些特别棘手,即使你能够提出它应该合法的例子(如你所知)。 (有许多情况看起来不合法(例如,如果有finally
块),或需要更复杂的收卷/退绕规则。)
答案 2 :(得分:2)
我在SO的其他地方找到了这个解决方案。基本上,使用return
fn
,以便fn
返回w / o异常时,您的函数也将如此。如果fn
抛出,并且异常满足条件n > 1
,则忽略异常并且在catch块之后发生递归调用。
@tailrec
def retry[T](n: Int)(fn: => T): T = {
try {
return fn
} catch {
case e if n > 1 => // fall through to retry below
}
retry(n - 1)(fn)
}