Domanda

Uso della riga di comando REPL di Scala:

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

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

Sembra che non sia possibile definire metodi ricorsivi sovraccarichi nella REPL. Ho pensato che si trattasse di un bug nel REPL di Scala e l'ho archiviato, ma è stato quasi immediatamente chiuso con " wontfix: non vedo in alcun modo che ciò possa essere supportato data la semantica dell'interprete, perché questi due metodi devono essere compilato insieme. " Ha raccomandato di inserire i metodi in un oggetto che racchiude.

Esiste un'implementazione del linguaggio JVM o un esperto di Scala che potrebbe spiegare perché? Vedo che sarebbe un problema se i metodi si chiamassero per esempio, ma in questo caso?

O se questa è una domanda troppo grande e pensi che io abbia bisogno di una maggiore conoscenza dei prerequisiti, qualcuno ha dei buoni collegamenti a libri o siti sulle implementazioni del linguaggio, specialmente sulla JVM? (Conosco il blog di John Rose e il libro Programming Language Pragmatics ... ma questo è tutto. :)

È stato utile?

Soluzione

Il problema è dovuto al fatto che l'interprete deve spesso sostituire elementi esistenti con un determinato nome, piuttosto che sovraccaricarli. Ad esempio, passerò spesso a sperimentare qualcosa, creando spesso un metodo chiamato test :

def test(x: Int) = x + x

Poco dopo, diciamo che sto eseguendo un diverso esperimento e creo un altro metodo chiamato test , non correlato al primo:

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

Questo non è uno scenario del tutto irrealistico. In realtà, è proprio come la maggior parte delle persone usa l'interprete, spesso senza nemmeno rendersene conto. Se l'interprete decidesse arbitrariamente di mantenere entrambe le versioni di test , ciò potrebbe portare a confondere differenze semantiche nell'uso di test. Ad esempio, potremmo effettuare una chiamata a test , passando accidentalmente un Int anziché List [Int] (non l'incidente più improbabile nel mondo):

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

Nel corso del tempo, l'ambito radice dell'interprete verrebbe incredibilmente ingombra di varie versioni di metodi, campi, ecc. Tendo a lasciare il mio interprete aperto per giorni alla volta, ma se fosse consentito un sovraccarico come questo, saremmo costretto a "svuotare" l'interprete ogni tanto quando le cose diventano troppo confuse.

Non è un limite del compilatore JVM o Scala, è una decisione progettuale deliberata. Come menzionato nel bug, puoi comunque sovraccaricare se ti trovi in ??qualcosa di diverso dall'ambito di root. Racchiudere i tuoi metodi di prova all'interno di una classe mi sembra la soluzione migliore.

Altri suggerimenti

% 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 accetterà se copi entrambe le righe e le incolli contemporaneamente.

Come mostrato dalla risposta di extempore , è possibile sovraccaricare. Il commento di Daniel sulla decisione di progettazione è corretto, ma, penso, incompleto e un po 'fuorviante. Non c'è fuorilegge di sovraccarichi (poiché sono possibili), ma non sono facilmente raggiungibili.

Le decisioni di progettazione che portano a questo sono:

  1. Tutte le definizioni precedenti devono essere disponibili.
  2. Viene compilato solo il codice appena immesso, invece di ricompilare tutto ciò che è stato inserito ogni volta.
  3. Deve essere possibile ridefinire le definizioni (come menzionato da Daniel).
  4. Deve essere possibile definire membri come vals e defs, non solo classi e oggetti.

Il problema è ... come raggiungere tutti questi obiettivi? Come elaboriamo il tuo esempio?

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

A partire dal quarto elemento, un val o def può essere definito solo all'interno di una classe , trait , oggetto o oggetto pacchetto . Quindi, REPL inserisce le definizioni all'interno degli oggetti, in questo modo ( rappresentazione non reale! )

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

Ora, a causa del funzionamento di JVM, una volta definito uno di essi, non è possibile estenderli. Ovviamente potresti ricompilare tutto, ma l'abbiamo scartato. Quindi è necessario posizionarlo in un posto diverso:

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

E questo spiega perché i tuoi esempi non sono sovraccarichi: sono definiti in due luoghi diversi. Se li mettessi sulla stessa linea, sarebbero tutti definiti insieme, il che li renderebbe sovraccarichi, come mostrato nell'esempio di extempore.

Come per le altre decisioni di progettazione, ogni nuovo pacchetto importa definizioni e "res" dai pacchetti precedenti e le importazioni possono oscurarsi a vicenda, il che rende possibile "ridefinire" roba.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top