2017-10-04 105 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必須不承擔內聯。