2014-03-01 23 views
1

考慮這個基本的Scala示例代碼:調用定義之前它們的功能(前向參考在變量的定義延伸)

object Test { 
    def main(args: Array[String]) { 
     inner() 

     var x: Int = 5 

     def inner() { 
      println("x: " + x) 
     } 
    } 
} 

試圖編譯它產生以下錯誤消息:

test.scala:3: error: forward reference extends over definition of variable x 
     inner() 
     ^
one error found 

問題:

  1. 什麼是t中的前向參考他的語境以及它對「變量x的定義的擴展」意味着什麼?
  2. 爲什麼前面的代碼會引發這個編譯時錯誤?
  3. 這個錯誤是如何被捕獲的?似乎編譯器必須實現一些類似解釋器的功能並遵循函數調用!

這個問題不是關於定義的順序,而是關於何時函數被調用。 在定義函數之前調用函數是完全合法的 - 但如果在調用和函數定義之間放置了一個變量,並且該函數使用此變量,它會突然變爲非法

我想要解釋這種語言功能!爲什麼在那裏?它是如何工作的?還有其他一些更復雜的例子嗎?也就是說,它只是其他功能的一部分,或者是某些規則的結果?

我想象中的編譯器目前正在做:

  1. 檢查的功能是可以訪問當前範圍的變量封閉,
  2. 檢查,如果它實際上訪問的變量在當前範圍和
  3. 每個封閉訪問,檢查變量調用

難道我基本上回答我的第三個問題之前定義的變量?這種行爲是如何工作的?這似乎使編譯器複雜化很多(特別是如果我們考慮具有多個功能級別的情況)。

如果是這樣的話,這是如何融入語言的正式定義,即語法?在我看來,我寫的程序在語法上是正確的。

+0

我想說,這是一個重複的問題,因爲它經常出現,但顯然其他的問題是什麼時候不是很明顯什麼前向引用是。 –

回答

5

http://www.scala-lang.org/docu/files/ScalaReference.pdf

一個名字的範圍由聲明或定義引入是包含綁定的整個語句序列 。然而,在塊中引用的正向 有限制:在組成塊的語句序列s1 ... sn中,如果si中的簡單 名稱指的是由sj定義的實體,其中j> = i,則對於所有包括si和sj在內的 ,

•sk不能作爲變量定義。

•如果SK是值德科幻定義,它必須是懶惰

3

此錯誤消息表示塊中的向前引用不被允許。在一個塊中,所有變量(或值)必須按照線性順序定義。

請注意,在類或對象中允許使用前向引用,但不允許在塊(方法定義)中使用前向引用。例如:

這將工作:

object Test { // forward reference is allowed in an object 
    def inner() { 
    println("x: " + x) 
    } 
    var x: Int = 5 
} 

這也將工作:

class Test { // forward reference is allowed in an class 
    def inner() { 
    println("x: " + x) 
    } 
    var x: Int = 5 
} 

但是這些都將工作:

def main() { // forward reference for vals or vars is not allowed in a block 
    inner() 

    def inner() { 
    println("x: " + x) 
    } 

    var x: Int = 5 
} 

def main() { 
    inner() 

    var x: Int = 5 

    def inner() { 
    println("x: " + x) 
    } 
} 

由於VAL 「X」是一個字段初始化語句,在初始化之前引用它是非法的(「x」在初始化之前包含null)。

中,爲了使其工作,你可以使用var更改爲懶惰VAL:

def main() { // forward reference for lazy vals is allowed in a block 
    def inner() { 
    println("x: " + x) 
    } 

    lazy val x: Int = 5 
} 

的法律例子調用懶VAL的方法偷懶VAL表達式之前稱爲:

def main() { 
    inner() 

    def inner() { 
    println("x: " + x) 
    } 

    lazy val x: Int = 5 
} 

如果沒有「x」懶惰,「x」會立即被初始化,這將打破Scala編譯器執行的重新檢查階段。

對於這種行爲背後的理論的一點點,你可以看看「階段refchecks」的部分:https://wiki.scala-lang.org/display/SIW/Overview+of+Compiler+Phases

+0

不是我正在尋找的答案。我直觀地理解這一點,但我想知道這種行爲背後的理論。例如,你說所有的變量都必須按順序定義,但是這並不能解釋你的第四個例子,在定義變量之後定義函數,並且編譯器知道存在錯誤的唯一方法是在被調用時實際查找函數定義,然後得出結論認爲在那裏調用該函數是非法的。 – corazza

+0

@yannbane:看看Scala編譯器階段。維克托莫羅茲的答案對它背後的理論有了更好的解釋。 –