2008-09-23 55 views
8

使用Scala的命令行REPL:遞歸超載語義 - JVM語言

def foo(x: Int): Unit = {} 
def foo(x: String): Unit = {println(foo(2))} 

error: type mismatch; 
found: Int(2) 
required: String 

看來你不能定義重載在REPL遞歸方法。我認爲這是Scala REPL中的一個bug,並提交了它,但它幾乎立即被關閉了,「wontfix:鑑於解釋器的語義,我沒有看到任何可以支持的方式,因爲必須編譯這兩個方法一起。」他建議將這些方法放在一個封閉的對象中。

是否有JVM語言實現或Scala專家誰可以解釋爲什麼?我可以看到,如果方法互相調用,但在這種情況下會出現問題?

或者,如果這個問題太大了,而且您認爲我需要更多的必備知識,那麼某人是否有任何有關語言實現的書籍或網站的良好鏈接,特別是在JVM上? (我知道約翰·羅斯的博客和編程語言語用學的書......但是就是這樣。:)

回答

11

這個問題是由於解釋器最經常需要用替換現有元素給定名稱,而不是重載它們。例如,我經常會通過與一些實驗運行,通常創建一個名爲test方法:

def test(x: Int) = x + x 

過了一會兒,讓我們說我運行一個不同實驗,我創建了一個名爲另一種方法test,無關的第一:

def test(ls: List[Int]) = (0 /: ls) { _ + _ } 

這並不是一個完全不現實的場景。實際上,大多數人使用解釋器的方式正是如此,甚至常常沒有意識到這一點。如果口譯人員隨意決定保留test這兩個版本的範圍,那麼這可能會導致混淆使用測試的語義差異。例如,我們可以打一個電話到test,偶然路過的Int而非List[Int](不是世界上最不可能的意外):

test(1 :: Nil) // => 1 
test(2)   // => 4 (expecting 2) 

隨着時間的推移,解釋器的根範圍會得到令人難以置信的混亂各種版本的方法,領域等等。我傾向於讓我的解釋器一次打開好幾天,但是如果允許這樣的重載,我們就會被迫頻繁地「刷新」解釋器,因爲事情太容易混淆了。

這不是JVM或Scala編譯器的限制,而是一個深思熟慮的設計決定。正如錯誤中提到的,如果你在根作用域之外,你仍然可以重載。在班級中包含您的測試方法似乎是對我來說最好的解決方案。

+0

優秀的答案Daniel,謝謝。另外,我喜歡你的博客。 :) – 2008-09-23 18:27:29

4

如果你同時複製兩行並粘貼兩者,REPL將接受。

5
% scala28 
Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_20). 
Type in expressions to have them evaluated. 
Type :help for more information. 

scala> def foo(x: Int): Unit =() ; def foo(x: String): Unit = { println(foo(2)) } 
foo: (x: String)Unit <and> (x: Int)Unit 
foo: (x: String)Unit <and> (x: Int)Unit 

scala> foo(5) 

scala> foo("abc") 
() 
1

extempore's所示,答案可能過載。 Daniel's關於設計決策的評論是正確的,但是,我認爲,不完整並且有點誤導。沒有超載過載(因爲它們是可能的),但它們不容易實現。

的設計決策導致這種是:

  1. 所有以前的定義必須是可用的。
  2. 只編譯新輸入的代碼,而不是重新編譯每次輸入的所有內容。
  3. 必須可以重新定義定義(如丹尼爾提到)。
  4. 必須可以定義vals和defs等成員,而不僅僅是類和對象。

問題是......如何實現所有這些目標?我們如何處理你的例子?

def foo(x: Int): Unit = {} 
def foo(x: String): Unit = {println(foo(2))} 

與第四項開始,A valdef只能一個classtrait,或object內部package object定義。所以,REPL提出的定義中的對象,像這樣(不實際表示!

package $line1 { // input line 
    object $read { // what was read 
    object $iw { // definitions 
     def foo(x: Int): Unit = {} 
    } 
    // val res1 would be here somewhere if this was an expression 
    } 
} 

現在,由於JVM是如何工作的,一旦你定義的其中之一,你不能擴展它們。當然,您可以重新編譯所有內容,但我們放棄了這一點。所以,你需要把它放在一個不同的地方:

package $line1 { // input line 
    object $read { // what was read 
    object $iw { // definitions 
     def foo(x: String): Unit = { println(foo(2)) } 
    } 
    } 
} 

這解釋了爲什麼你的例子並不重載:它們是在兩個不同的地方定義。如果你把它們放在同一行中,它們都將被一起定義,這會使它們重載,如extempore的例子所示。

至於其他設計決策,每個新軟件包導入定義和來自先前軟件包的「res」,並且導入可以相互影響,這使得可以「重新定義」東西。