簡體   English   中英

Scala REPL中的遞歸重載語義--JVM語言

[英]Recursive overloading semantics in the Scala REPL - JVM languages

使用Scala的命令行REPL:

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

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

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

是否有JVM語言實現或Scala專家可以解釋原因? 我可以看到,如果這些方法相互調用,那將是一個問題,但在這種情況下?

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

問題是由於解釋器通常必須用給定名稱替換現有元素,而不是重載它們。 例如,我經常會嘗試一些東西,經常創建一個名為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編譯器的限制,而是一個深思熟慮的設計決策。 正如bug中所提到的,如果你在root范圍之外的其他東西,你仍然可以重載。 將測試方法包含在一個類中似乎是對我來說最好的解決方案。

% 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")
()

如果您復制兩行並同時粘貼,則REPL將接受。

臨時演員的答案所示,可能會超載。 丹尼爾關於設計決策評論是正確的,但我認為不完整且有點誤導。 沒有超載的禁止 (因為它們是可能的),但它們並不容易實現。

導致這種情況的設計決策是:

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

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

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

從第4項開始,只能在classtraitobjectpackage object定義valdef 所以,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)) }
    }
  }
}

這就解釋了為什么你的例子不是重載:它們在兩個不同的地方定義。 如果將它們放在同一行中,它們將被一起定義,這將使它們重載,如示例中所示。

至於其他設計決策,每個新包導入定義和以前包中的“res”,並且導入可以相互影響,這使得“重新定義”東西成為可能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM