2009-10-17 5 views
9

In Scala, è possibile sovraccaricare un metodo disponendo di metodi che condividono un nome comune, ma che hanno origini diverse o tipi di parametri diversi. Mi stavo chiedendo perché questo non è stato esteso anche al tipo di ritorno di un metodo? Considera il seguente codice:Perché l'overloading del metodo non è definito per tipi di reso diversi?

class C { 
    def m: Int = 42 
    def m: String = "forty two" 
} 

val c = new C 
val i: Int = C.m 
val s: String = C.m 

C'è un motivo per cui questo non dovrebbe funzionare?

Grazie,

Vincent.

+0

Vedere anche Java specifica http://stackoverflow.com/questions/2744511/java-why-no-return-type-based-method-overloading?lq=1 – nawfal

+0

e in generale http : //stackoverflow.com/questions/442026/function-overloading-by-return-type – nawfal

risposta

5

Ovviamente è possibile eseguire il sovraccarico per i metodi che differiscono per tipo di ritorno, ma non per i metodi che differiscono solo per tipo di ritorno.. Ad esempio, questo va bene:

def foo(s: String) : String = s + "Hello" 
def foo(i: Int) : Int = i + 1 

A parte questo, la risposta alla tua domanda è evidentemente che si trattava di una decisione di progettazione: il tipo di ritorno fa parte della firma del metodo come chiunque abbia sperimentato un AbstractMethodError può dire .

considerare tuttavia, come ad esempio consentendo sovraccarico potrebbe funzionare in tandem con sub-tipizzazione:

class A { 
    def foo: Int = 1 
} 
val a: A = //...lookup an A 
val b = a.foo 

Ciò è perfettamente codice valido di corso javac avrebbe risolto in modo univoco la chiamata di metodo. Ma cosa succede se io sottoclasse A come segue:

class B extends A { 
    def foo: String = "Hello" 
} 

Questo fa sì che la risoluzione del codice originale di quale metodo viene chiamato per essere infrante. Cosa dovrebbe essere b? Ho logicamente rotto qualche codice esistente sottotitolando alcune classi esistenti, anche se non ho cambiato né quel codice né quella classe.

+2

Il tipo statico di a è A, e A ha solo un metodo chiamato foo (quello che restituisce Int), quindi dov'è l'ambiguità? –

2

Non ho mai usato scala, quindi qualcuno mi ha colpito sulla testa se ho sbagliato qui, ma questa è la mia opinione.

Supponiamo che tu abbia due metodi le cui firme differiscono solo per tipo di ritorno.

Se si chiama quel metodo, come fa il compilatore (interprete?) A sapere quale metodo si desidera effettivamente chiamare?

Sono sicuro che in alcune situazioni potrebbe essere in grado di capirlo, ma cosa succede se, ad esempio, uno dei tipi di ritorno è una sottoclasse dell'altro? Non è sempre facile.

Java non consente il sovraccarico dei tipi di ritorno e poiché scala è costruita sulla JVM java, è probabilmente solo una limitazione java.

(Modifica) Si noti che i ritorni Covarianti sono un altro problema. Quando si sostituisce un metodo, è possibile scegliere di restituire una sottoclasse della classe che si desidera restituire, ma non è possibile scegliere una classe non correlata da restituire.

1

Per distinguere tra diverse funzioni con lo stesso nome e tipi di argomenti, ma tipi di ritorno diversi, è necessaria una certa sintassi o un'analisi del sito di un'espressione.

Scala è un linguaggio orientato all'espressione (ogni istruzione è un'espressione). In genere i linguaggi orientati all'espressione preferiscono che la semantica delle espressioni dipenda solo dalla valutazione dell'ambito, non dall'accesso al risultato, quindi per l'espressione foo() in i_take_an_int(foo()) e i_take_any_type (foo()) e foo() come una dichiarazione tutti chiamano la stessa versione di foo() .

C'è anche il problema che aggiungendo un sovraccarico di tipo restituito a una lingua con inferenza di tipo renderà il codice completamente incomprensibile - dovresti tenere a mente una quantità incredibile del sistema per prevedere cosa accadrà quando il codice viene eseguito.

6

Il motivo principale sono i problemi di complessità: con un approccio del compilatore "normale", si va in-out (dall'espressione interna allo scope esterno), costruendo il proprio passo per passo; se si aggiunge la differenziazione di tipo return-only, è necessario passare a un approccio di backtracking, che aumenta notevolmente il tempo di compilazione, la complessità del compilatore (= bug!).

Inoltre, se si restituisce un sottotipo o un tipo che può essere convertito automaticamente nell'altro, quale metodo si dovrebbe scegliere? Daresti errori di ambiguità per codice perfettamente valido.

Non ne vale la pena.

Tutto sommato, è possibile eseguire facilmente il refactoring del codice per evitare un overload di tipo restituito, ad esempio aggiungendo un parametro dummy del tipo che si desidera restituire.

7

In realtà, puoi farlo funzionare con la magia di "implicito". Come segue:

scala> case class Result(i: Int,s: String) 

scala> class C { 
    |  def m: Result = Result(42,"forty two") 
    | } 

scala> implicit def res2int(res: Result) = res.i 

scala> implicit def res2str(res: Result) = res.s 

scala> val c = new C 

scala> val i: Int = c.m 

i: Int = 42 


scala> val s: String = c.m 

s: String = forty two 

scala> 
+0

Esattamente quello che stavo cercando, grazie – MikeB

1

Tutte le risposte che dicono che la JVM non consente che questo sia errato. È possibile sovraccaricare in base al tipo restituito. Sorprendentemente, la JVM fa sì che ciò sia permesso da; sono i compilatori per le lingue che vengono eseguite sulla JVM che non lo consentono. Ma ci sono modi per aggirare i limiti del compilatore in Scala.

Ad esempio, si consideri il seguente frammento di codice:

object Overload{ 
    def foo(xs: String*) = "foo" 
    def foo(xs: Int*) = "bar" 
} 

Questo sarà lanciare un errore compilatore (Poiché varargs, indicati dal * dopo il tipo di argomento, tipo cancelli a Seq):

Error:(217, 11) double definition: 
def foo(xs: String*): String at line 216 and 
def foo(xs: Any*): String at line 217 
have same type after erasure: (xs: Seq)String 
     def foo(xs: Any*) = "bar"; 

Tuttavia, se si modifica il valore del secondo foo a 3 anziché bar (in tal modo, modificare il tipo di ritorno da String a Int) come segue:

object Overload{ 
    def foo(xs: String*) = "foo" 
    def foo(xs: Int*) = 3 
} 

... si volontà non ottenere un errore di compilazione.

modo da poter fare qualcosa di simile:

val x: String = Overload.foo() 
val y: Int = Overload.foo() 

println(x) 
println(y) 

e si stamperà:

3 
foo 

Tuttavia, l'avvertenza di questo metodo è dover aggiungere varargs come l'ultimo (o solo argomento per le funzioni sovraccariche, ognuna con un proprio tipo distinto.

Fonte: http://www.drmaciver.com/2008/08/a-curious-fact-about-overloading-in-scala/