2008-09-23 21 views
8

Mit Scala Befehlszeile REPL:rekursive Überlastung Semantik in der Scala REPL - JVM Sprachen

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

gibt

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

Es scheint, dass Sie nicht rekursive Methoden in der REPL überlastet definieren. Ich dachte, das sei ein Fehler in der Scala REPL und habe ihn abgelegt, aber er wurde fast augenblicklich mit "wontfix" geschlossen: Ich sehe keine Möglichkeit, dass dies angesichts der Semantik des Interpreters unterstützt werden könnte, da diese beiden Methoden kompiliert werden müssen zusammen." Er empfahl, die Methoden in ein umschließendes Objekt zu setzen.

Gibt es eine JVM-Sprachimplementierung oder einen Scala-Experten, der erklären könnte, warum? Ich kann sehen, dass es ein Problem wäre, wenn sich die Methoden gegenseitig aufrufen würden, aber in diesem Fall?

Oder wenn das eine zu große Frage ist und Sie denken, dass ich mehr Vorwissen benötige, hat jemand gute Links zu Büchern oder Seiten über Sprachimplementierungen, besonders auf der JVM? (Ich weiß über John Rose Blog und das Buch Programmiersprache Pragmatics ... aber das ist es. :)

Antwort

11

Das Problem ist aufgrund der Tatsache, dass der Interpreter am häufigsten vorhandene Elemente mit einem bestimmten Namen ersetzt, anstatt sie überlasten. Zum Beispiel werde ich oft durch das Experimentieren mit etwas oft ausgeführt werden, eine Methode namens test erstellen:

def test(x: Int) = x + x 

Ein wenig später, sagen wir mal, dass ich ein verschiedenen Experiment renne und ich erstellen Sie eine andere Methode mit dem Namen test, in keinem Zusammenhang mit dem ersten:

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

Dies ist kein völlig unrealistisches Szenario. Genau genommen benutzen die meisten Leute den Dolmetscher, oft ohne es zu merken. Wenn der Interpreter willkürlich beschlossen hat, beide Versionen von test im Geltungsbereich zu behalten, könnte dies zu verwirrenden semantischen Unterschieden bei der Verwendung von Tests führen. Zum Beispiel könnten wir einen Anruf zu test machen, versehentlich ein Int statt List[Int] (nicht der unwahrscheinlichsten Unfall in der Welt) vorbei:

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

Im Laufe der Zeit würde der Stammbereich des Interpreters unglaublich unübersichtlich bekommen verschiedene Versionen von Methoden, Feldern usw. Ich neige dazu, meinen Interpreter für Tage offen zu lassen, aber wenn eine Überladung wie diese erlaubt wäre, wären wir gezwungen, den Interpreter gelegentlich zu "leeren", wenn die Dinge zu verwirrend werden .

Es ist keine Einschränkung der JVM oder des Scala-Compilers, es ist eine bewusste Designentscheidung. Wie im Fehler erwähnt, können Sie immer noch überladen, wenn Sie sich in etwas anderem als dem Root-Bereich befinden. Es scheint mir die beste Lösung zu sein, Ihre Testmethoden innerhalb einer Klasse einzuschließen.

+0

Ausgezeichnete Antwort Daniel, danke. Außerdem mag ich dein Blog. :) –

4

REPL wird akzeptieren, wenn Sie beide Zeilen kopieren und beide zur gleichen Zeit einfügen.

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

Wie von extempore's Antwort gezeigt, ist es möglich, zu überlasten. Daniel's Kommentar über Design Entscheidung ist richtig, aber ich denke, unvollständig und ein bisschen irreführend. Es gibt keine Outlawing Überlastungen (da sie möglich sind), aber sie sind nicht leicht zu erreichen.

Die Design-Entscheidungen, die dazu führen, sind:

  1. Alle bisherigen Definitionen zur Verfügung stehen müssen.
  2. Nur neu eingegebener Code wird kompiliert, anstatt alles neu zu kompilieren, das jemals eingegeben wurde.
  3. Es muss möglich sein, Definitionen neu zu definieren (wie Daniel erwähnt).
  4. Es muss möglich sein, Member wie Vals und Defs zu definieren, nicht nur Klassen und Objekte.

Das Problem ist ... wie alle diese Ziele zu erreichen? Wie bearbeiten wir Ihr Beispiel?

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

mit dem vierten Element Ab, A val oder def kann nur innerhalb eines class, definiert werden trait, object oder package object. So REPL die Definitionen innerhalb Objekte legt, wie diese (nicht unbedingt der Darstellung!)

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 
    } 
} 

nun darauf zurückzuführen, wie JVM arbeitet, wenn Sie einer von ihnen definiert sind, können Sie sie nicht verlängern. Du könntest natürlich alles neu kompilieren, aber wir haben das verworfen. So müssen Sie es an einem anderen Ort Ort:

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

Und dies erklärt, warum Ihre Beispiele sind nicht Überlastungen: sie an zwei verschiedenen Stellen definiert ist. Wenn Sie sie in die gleiche Zeile setzen, werden sie alle zusammen definiert, was zu Überladungen führt, wie im Beispiel von extempore gezeigt.

Wie bei den anderen Design-Entscheidungen, jedes neue Paket importieren Definitionen und "res" von früheren Paketen, und die Importe können sich gegenseitig Schatten, was es ermöglicht, "Sachen neu zu definieren".