2017-10-04 112 views
1

以下是引发编译错误的最小代码:“不在尾部位置的递归调用”。但是,我使用的是@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

可能的重复https://stackoverflow.com/questions/25582515/make-method-actually-inline –

回答

2

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

此外,使用@inline注释功能不会保证优化程序将内联它,只是它会“特别努力地尝试”。

+0

我非常喜欢你的答案。它强调提问者希望'@ inline'能够消除额外的堆栈帧。 –

+0

然后我很好地使用宏。 –

+0

您是否有任何参考文献证明@inline实际上仍然将参数推入堆栈,而不是像我所期望的那样将其推送到局部变量? –

2

嗯,尾递归是如何在JVM现实化的机制,方式如下解释:

斯卡拉,在尾递归的情况下,可以消除 新的堆栈帧的创建并重新使用当前的堆栈帧。无论递归调用次数为 多少次,堆栈 都不会变得更深。

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

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

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

注意:我也相信@inline在这里没有什么可做的,在这个例子中不是必需的,因为如果我删除它 - 编译器仍抱怨有同样的原因。

+0

对'reccall'的调用不应该手动内联,因为它在我的实际示例中会考虑很多代码。请理解这是一个简化的代码,而不是我的完整代码。 –

+0

@MikaëlMayer,我现在明白了。我最初并不知道你认为'@ inline'会消除这个堆栈帧,并且会通过局部变量推送参数。现在我明白了。 –

0

这里的问题是,@inline是严格的建议:它不能保证编译器将内联函数。由于@tailrec只有在绝对保证可以消除尾部呼叫时才有效,这意味着使用@tailrec必须不承担内联。