在下文中,行maybeNext.map{rec}.getOrElse(n)
使用Option
monad来实现递归或转义模式。
scala> @tailrec
| def rec(n: Int): Int = {
| val maybeNext = if (n >= 99) None else Some(n+1)
| maybeNext.map{rec}.getOrElse(n)
| }
看起来不错:
<console>:7: error: could not optimize @tailrec annotated method:
it contains a recursive call not in tail position
def rec(n: Int): Int = {
^
我认为编译器应该能够在这种情况下对尾递归进行排序。它等同于以下(有些令人厌恶但可编译)的样本:
scala> @tailrec
| def rec(n: Int): Int = {
| val maybeNext = if (n >= 99) None else Some(n+1)
| if (maybeNext.isEmpty) n
| else rec(maybeNext.get)
| }
rec: (n: Int)Int
这里有人能提供照明吗?为什么编译器无法解决这个问题?这是一个错误,还是一个疏忽?问题太难了吗?
编辑:从第一个示例中删除@tailrec
并编译方法;循环终止。最后一次通话始终为getOrElse
,相当于if option.isEmpty defaultValue else recurse
。我认为这可以而且应该由编译器推断。
答案 0 :(得分:8)
这不是一个错误,它不是一个疏忽,它不是一个尾递归。
是的,您可以以尾递归方式编写代码,但这并不意味着每个等效算法都可以进行尾递归。我们来看看这段代码:
maybeNext.map{rec].getOrElse(n)
首先,最后一次通话是getOrElse(n)
。此调用不是可选的 - 它始终是,并且必须调整结果。但是我们忽略它。
倒数第二个电话是map{rec}
。 不到rec
。实际上,rec
在您的代码中并未被称为 !其他一些函数调用它(事实上,它不是map
上的最后一次调用),但不是你的函数。
对于尾部递归的东西,你需要能够用“goto”替换这个调用,可以这么说。像这样:
def rec(n: Int): Int = {
BEGINNING:
val maybeNext = if (n >= 99) None else Some(n+1)
if (maybeNext.isEmpty) n
else {
n = maybeNext.get
goto BEGINNING
}
}
如何在其他代码中发生这种情况?
def rec(n: Int): Int = {
BEGINNING:
val maybeNext = if (n >= 99) None else Some(n+1)
maybeNext.map{x => n = x; goto BEGINNING}.getOrElse(n)
}
此处的转到<{1>}内的不是。它位于匿名rec
的{{1}}内,而它位于Function1
的{{1}}内,因此此处的分支会留下两个堆栈帧每次通话。假设首先可以进行方法间分支。