这是一个最小的代码,它会引发编译错误'递归调用不在尾部位置'。但是,我使用@inline
并且尾部位置的递归调用是。我之所以使用此@inline
,是因为我将原始reccall
的代码复制了两次。
import scala.annotation._
object Test {
@tailrec private def test(i: Int): Int = {
@inline def reccall(i: Int): Int = test(i-1)
i match {
case 0 => 0
case i => reccall(i)
}
}
}
我已查看了Recursive call not in tail position @tailrec why does this method not compile with 'contains a recursive call not in tail position'?的答案,但它们并不适用于我的案例。使用Scala 2.12
答案 0 :(得分:2)
嗯,在JVM中如何实现尾递归的机制可以通过以下方式解释:
在尾递归的情况下,Scala可以消除a的创建 新的堆栈帧,只需重新使用当前的堆栈帧。堆栈 无论递归调用多少次,都不会更深入 制成。
所以在你的情况下,它不能重用属于test
方法的当前堆栈帧,因为它必须为reccall
方法创建一个新的堆栈帧。
在这种情况下,递归调用是隐式的,由另一个方法构成。所以我相信你不能真正为这种情况实现尾递归。
您可以完全删除reccall
方法并编写case i => test(i-1)
,然后编译器不会抱怨。
注意:我也相信@inline
在这里没有任何关系,在这个例子中并不重要,因为如果我删除它 - 编译器仍抱怨同样的原因。
答案 1 :(得分:2)
看来,实现@inline
的方式是它仍然通过堆栈传递参数。通过插入内联代码消除了跳转,但堆栈仍然用于参数。这使得无法处于尾部位置,因为在呼叫完成后需要清理堆栈。
此外,使用@inline
注释函数并不保证优化器将内联它,只是它会“特别努力”。
答案 2 :(得分:0)
这里的问题是@inline
严格建议:它不保证编译器将内联函数。由于@tailrec
只有在绝对保证可以消除尾调用的情况下才有效,这意味着使用@tailrec
必须假定没有内联。