无法优化方法

时间:2017-10-04 08:24:01

标签: scala inline tail-recursion

这是一个最小的代码,它会引发编译错误'递归调用不在尾部位置'。但是,我使用@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

3 个答案:

答案 0 :(得分:2)

嗯,在JVM中如何实现尾递归的机制可以通过以下方式解释:

  

在尾递归的情况下,Scala可以消除a的创建   新的堆栈帧,只需重新使用当前的堆栈帧。堆栈   无论递归调用多少次,都不会更深入   制成。

所以在你的情况下,它不能重用属于test方法的当前堆栈帧,因为它必须为reccall方法创建一个新的堆栈帧。

在这种情况下,递归调用是隐式的,由另一个方法构成。所以我相信你不能真正为这种情况实现尾递归。

您可以完全删除reccall方法并编写case i => test(i-1),然后编译器不会抱怨。

注意:我也相信@inline在这里没有任何关系,在这个例子中并不重要,因为如果我删除它 - 编译器仍抱怨同样的原因。

答案 1 :(得分:2)

看来,实现@inline的方式是它仍然通过堆栈传递参数。通过插入内联代码消除了跳转,但堆栈仍然用于参数。这使得无法处于尾部位置,因为在呼叫完成后需要清理堆栈。

此外,使用@inline注释函数并不保证优化器将内联它,只是它会“特别努力”。

答案 2 :(得分:0)

这里的问题是@inline严格建议:它不保证编译器将内联函数。由于@tailrec只有在绝对保证可以消除尾调用的情况下才有效,这意味着使用@tailrec必须假定没有内联。