Scala Funktion Variance und Übergeordnete
-
28-09-2019 - |
Frage
Ich bin ein kleines Problem mit den Methoden verstehe Varianz bei Überlastung.
Während diese perfekt funktioniert aufgrund Kovarianz im Rückgabetyp
class Bla
class Fasel extends Bla
trait Test[A] {
def tester(): Bla = new Bla
}
class FooTest[A](a: A) extends Test[A] {
override def tester(): Fasel = new Fasel
}
diese ausfällt, obwohl Funktionen kontra in ihrer Parametertypen.
class Bla
class Fasel extends Bla
trait Test[A] {
def tester(a: Fasel): Bla = new Bla
}
class FooTest[A](a: A) extends Test[A] {
override def tester(a: Bla): Fasel = new Fasel
}
Was bekomme ich hier falsch? Alle Zeiger?
Viele Grüße, raichoo
Lösung
Es gibt zwei Dinge, die sich hier:
- Eine Funktion und ein Verfahren ist nicht dasselbe
- Methoden nicht polymorph sind in ihren Parametern Typen
Ihre tester
Methode ist eine Methode, keine Function1
. Es kann in eine Funktion mit der Unterstrich-Syntax angehoben werden:
val f = (new FooTest[String]).tester _ // Fasel => Bla
Diese Funktion ist kontravarianten in seinem Eingangstyp. (Es lohnt sich sagen jedoch, dass Funktionen nicht parametrierbar und auch wert, dass ich hatte eine Instanz von Foo
oder FooTest
haben, um ein Funktionsobjekt für die tester
Methode zu erhalten. Das ist natürlich folgt von der ersten Beobachtung!)
Eine Funktion ist ein Objekt, es kann nicht außer Kraft gesetzt , wie das macht keinen Sinn. Methoden außer Kraft gesetzt werden können. Aber, wie ich oben gesagt, ist die übergeordnete nicht polymorph in den Parametertyp Methode. So zum Beispiel:
class A {
def foo(a : Any) = println("A: " + a)
}
class B extends A {
override def foo(s : String) = println("B " + s) //will not compile!
}
Die beiden Verfahren, die in meinem oben genannten Beispiel sind zwei verschiedene Methoden. Dynamische Dispatch funktioniert nur auf dem Ziel-Methode (das heißt das Objekt, auf dem sie aufgerufen wird)
In dem obigen Beispiel, wenn Sie die override
Erklärung entfernen, wird der Code kompiliert. Wenn Sie die folgende ausführen:
(new B).foo(1) //prints A 1
(new B).foo("s") //prints B s
Das ist, weil, obwohl beide Methoden foo
genannt werden, sind sie völlig unterschiedliche Methoden (das heißt ich habe überladene foo
, nicht außer Kraft gesetzt it). Es ist am besten verstanden als dass ein Verfahren Argument (inkl deren Typen) Teil dieses Verfahrens ist einzigartig name . Eine Methode überschreibt andere nur, wenn sie genau das gleiche name .
Im Wesentlichen haben Sie verwirrt, was sind zwei getrennt und un bezogenen die Dinge in Ihrer Frage, die ich für Klarheit hinstellen wird:
- Die Varianz Annotationen auf
Function1
definieren, was es bedeutet, für eine Funktion ein Untertyp eines anderen zu sein (und damit zuweisbaren zu einem Referenz eines bestimmten Typs). - Methoden können auf Unterklassen außer Kraft gesetzt werden und die Sprachspezifikation Umrissen Regeln dafür, wann eine solche übergeordnete stattfindet.
Andere Tipps
Die entsprechenden Schnipsel der Spezifikation:
Method Typen
Verfahren Typ bezeichnet wird intern als
(Ps)U
, wo(Ps)
ist eine Folge von Parameternamen und -arten für einige(p1 :T1,...,pn :Tn)
n≥0
undU
ist ein (Wert oder Methode) Typ. Diese Art stellt genannten Methoden, die Argumente genanntp1, ..., pn
von TypenT1,...,Tn
und diese Rückkehr ein Resultat vom TypU
nehmen.Methodentypen existieren nicht als Arten von Werten. Wenn ein Methodenname als ein Wert verwendet wird, wird sein Typ implizit in einen entsprechenden Funktionstyp umgewandelt (§6.26).
Übergeordnete
Ein Mitglied
M
die KlasseC
dass Streichhölzer (§5.1.3) ein nicht-privates MitgliedM′
eine Basisklasse vonC
gesagt, dass Mitglied außer Kraft setzen. In diesem Fall wird die übergeordnete MitgliedM
muss die Bindung subsume (§3.5.2) die Bindung des überschriebenen MitgliedM′
.
Conformance
Wenn
Ti ≡ Ti′
füri = 1, ..., n
undU
KonformU′
die Methode Typ(p1 : T1,...,pn :Tn)U
Konform(p1′ :T1′,...,pn′ :Tn′)U′
dann.
subsumiert
Eine Erklärung oder Definition in irgendeiner Verbindung Art des Klassentyp
C
subsumiert eine weitere Erklärung des gleichen Namen in irgendeiner Verbindung Typ oder KlassentypC′
, wenn eines der folgenden gilt.
- Ein Wert Erklärung oder Definition, die definiert, ein Name x mit Typ T einen Wert oder Methodendeklaration subsumiert, die definiert
x
mit TypT′
, vorausgesetztT <: T′
.
Sie können außer Kraft setzen und den Rückgabetyp auf einen Subtyp ändern, aber während geordneter Typ für Argument akzeptieren, das Substitutionsprinzip genügen würde, ist es nicht erlaubt (dies ist ebenso wie in Java) Der Grund dafür ist, dass Sie auch Methoden überlasten (mehrere Methoden mit dem gleichen Namen, zählen und Typen unterschiedliche Argumente) und Ihre Methode wird eine Überlastung considerered werden. Ich denke, dies ist in erster Linie eine Frage der JVM-Kompatibilität und eine angemessene spec hat. Eine Überlastung macht bereits die scala spec ziemlich kompliziert. Einfach Routing könnten die überschriebene Methode, um die überlasteten eine mit der geänderten Signatur gut genug sein:
class FooTest[A] extends Test[A] {
override def test(a: Fasel) : Fasel = test(a.asInstanceOf[Bla])
def test(a: Bla) : Fasel = new Fasel
}
Was Sie tun können, ist eine Art Parameter kontra machen, in erscheint vorgesehen nur in kontra Position (vereinfacht, erscheint als Argument Typ und nicht als Ergebnistyp), aber es ist ganz anders:
trait Test[-A] {
// note the - before A.
// You might want to constraint with -A >: Fasel
def tester(a: A) : Bla = new Bla
}
class FooTest extends Test[Bla] {
override def tester(a: Bla): Fasel = new Fasel
}
val testOfBla: Test[Bla] = new FooTest
val testOfFasel: Test[Fasel] = testOfBla
// you can assign a Test[Bla] to a test[Fasel] because of the -A
Nun, in Ihrem zweiten Beispiel die Unterschrift von tester()
in Test
erklärt ein Fasel
Argument aber mit der überschriebenen Signatur von FooTest
tester()
ist mit einem Bla
als Argument erklärt. Da Fasel
ein Subtyp von Bla
durch ihren extend
s ist Hierarchie dies wahrscheinlich falsch ist.