Was ist der Unterschied zwischen den folgenden generischen Definitionen in Scala:
class Foo[T <: List[_]]
und
class Bar[T <: List[Any]]
Mein Bauch sagt mir, dass sie ungefähr gleich sind, aber dass letzteres expliziter ist. Ich finde Fälle, in denen das erstere kompiliert, das letztere jedoch nicht, aber den genauen Unterschied nicht erkennen kann.
Vielen Dank!
Bearbeiten:
Kann ich noch einen in die Mischung werfen?
class Baz[T <: List[_ <: Any]]
scala
generics
covariance
any
existential-type
Sean Connolly
quelle
quelle
<: Any
niemals etwas ändert. Jeder Typ in Scala ist<: Any
.Antworten:
OK, ich dachte mir, ich sollte meine Meinung dazu haben, anstatt nur Kommentare zu posten. Entschuldigung, dies wird lange dauern, wenn Sie die TL; DR bis zum Ende überspringen möchten.
Wie Randall Schulz sagte, ist hier
_
eine Abkürzung für einen existenziellen Typ. Nämlich,class Foo[T <: List[_]]
ist eine Abkürzung für
class Foo[T <: List[Z] forSome { type Z }]
Beachten Sie, dass im Gegensatz zu Randall Shulz 'Antwort (vollständige Offenlegung: Ich habe es auch in einer früheren Version dieses Beitrags falsch verstanden, danke an Jesper Nordenberg für den Hinweis) dies nicht dasselbe ist wie:
class Foo[T <: List[Z]] forSome { type Z }
noch ist es das gleiche wie:
class Foo[T <: List[Z forSome { type Z }]]
Achtung, es ist leicht, etwas falsch zu machen (wie mein früherer Fehler zeigt): Der Autor des Artikels, auf den Randall Shulz 'Antwort verweist, hat es selbst falsch verstanden (siehe Kommentare) und später behoben. Mein Hauptproblem bei diesem Artikel ist, dass in dem gezeigten Beispiel die Verwendung von Existentials uns vor einem Tippproblem bewahren soll, dies jedoch nicht. Überprüfen Sie den Code und versuchen Sie,
compileAndRun(helloWorldVM("Test"))
oder zu kompilierencompileAndRun(intVM(42))
. Ja, kompiliert nicht. EinfachcompileAndRun
generisch machenA
würde den Code Kompilierung machen, und es wäre viel einfacher sein. Kurz gesagt, das ist wahrscheinlich nicht der beste Artikel, um etwas über Existentiale und deren Zweck zu erfahren (der Autor selbst bestätigt in einem Kommentar, dass der Artikel "aufgeräumt werden muss").Daher würde ich eher empfehlen, diesen Artikel zu lesen: http://www.artima.com/scalazine/articles/scalas_type_system.html , insbesondere die Abschnitte "Existenzielle Typen" und "Varianz in Java und Scala".
Der wichtige Punkt, den Sie aus diesem Artikel erhalten sollten, ist, dass Existenziale nützlich sind (abgesehen davon, dass sie mit generischen Java-Klassen umgehen können), wenn es um nicht kovariante Typen geht. Hier ist ein Beispiel.
case class Greets[T]( private val name: T ) { def hello() { println("Hello " + name) } def getName: T = name }
Diese Klasse ist generisch (beachten Sie auch, dass sie unveränderlich ist), aber wir können sehen, dass
hello
der Typparameter (im Gegensatz zugetName
) wirklich nicht verwendet wird. Wenn ich also eine Instanz von bekomme,Greets
sollte ich sie immer aufrufen können, wie auch immerT
ist. Wenn ich eine Methode definieren möchte, die eineGreets
Instanz nimmt und nur ihrehello
Methode aufruft , könnte ich Folgendes versuchen:def sayHi1( g: Greets[T] ) { g.hello() } // Does not compile
Sicher genug, dies kompiliert nicht, wie
T
hier aus dem Nichts kommt.OK, dann machen wir die Methode generisch:
def sayHi2[T]( g: Greets[T] ) { g.hello() } sayHi2( Greets("John")) sayHi2( Greets('Jack))
Großartig, das funktioniert. Wir könnten hier auch Existentials verwenden:
def sayHi3( g: Greets[_] ) { g.hello() } sayHi3( Greets("John")) sayHi3( Greets('Jack))
Funktioniert auch. Alles in allem gibt es hier also keinen wirklichen Vorteil, wenn ein existenzieller (wie in
sayHi3
) gegenüber einem Typparameter (wie insayHi2
) verwendet wird.Dies ändert
Greets
sich jedoch, wenn es selbst als Typparameter für eine andere generische Klasse angezeigt wird. Angenommen, wir möchten mehrere Instanzen vonGreets
(mit unterschiedlichenT
) in einer Liste speichern . Lass es uns versuchen:val greets1: Greets[String] = Greets("John") val greets2: Greets[Symbol] = Greets('Jack) val greetsList1: List[Greets[Any]] = List( greets1, greets2 ) // Does not compile
Die letzte Zeile wird nicht kompiliert, da sie
Greets
unveränderlich ist, sodass aGreets[String]
undGreets[Symbol]
nicht alsGreets[Any]
obwohl behandelt werden kannString
undSymbol
beide erweitert werdenAny
.OK, versuchen wir es mit einem Existenziellen unter Verwendung der Kurzschreibweise
_
:val greetsList2: List[Greets[_]] = List( greets1, greets2 ) // Compiles fine, yeah
Dies lässt sich gut kompilieren, und Sie können wie erwartet Folgendes tun:
Denken Sie jetzt daran, dass der Grund, warum wir überhaupt ein Problem mit der Typprüfung hatten, darin bestand, dass
Greets
es unveränderlich ist. Wenn es in eine kovariante Klasse (class Greets[+T]
) umgewandelt worden wäre, hätte alles sofort geklappt und wir hätten niemals Existenziale benötigt.Zusammenfassend lässt sich sagen, dass Existentials nützlich sind, um mit generischen invarianten Klassen umzugehen. Wenn die generische Klasse jedoch nicht als Typparameter für eine andere generische Klasse angezeigt werden muss, benötigen Sie wahrscheinlich keine Existentials und fügen einfach einen Typparameter hinzu zu Ihrer Methode wird funktionieren
Kommen Sie nun (endlich, ich weiß!) Auf Ihre spezifische Frage zurück
class Foo[T <: List[_]]
Weil
List
es kovariant ist, ist dies in jeder Hinsicht dasselbe wie nur zu sagen:class Foo[T <: List[Any]]
In diesem Fall ist die Verwendung einer der beiden Notationen also nur eine Frage des Stils.
Wenn Sie jedoch ersetzen
List
mitSet
, Dinge zu ändern:class Foo[T <: Set[_]]
Set
ist invariant und somit befinden wir uns in der gleichen Situation wie bei derGreets
Klasse aus meinem Beispiel. Somit unterscheidet sich das Obige wirklich sehr vonclass Foo[T <: Set[Any]]
quelle
class Foo[T <: List[_]]
ist eine Abkürzung fürclass Foo[T <: List[Z] forSome { type Z }]
. Der gleiche WegList[Greets[_]]
ist eine Abkürzung fürList[Greets[Z] forSome { type Z }]
(und nichtList[Greets[Z]] forSome { type Z }
).]
reinclass Foo[T <: List[Z forSome { type Z }]
?]
am Ende fehlte etwas . Das hätte sein sollenclass Foo[T <: List[Z forSome { type Z }]]
. Jetzt behoben, danke.Ersteres ist eine Abkürzung für einen existenziellen Typ, wenn der Code nicht wissen muss, um welchen Typ es sich handelt, oder ihn einschränken muss:
class Foo[T <: List[Z forSome { type Z }]]
Dieses Formular besagt, dass der Elementtyp von
List
eher unbekannt istclass Foo
als Ihr zweites Formular, das speziell angibt, dass der Elementtyp desList
Elements lautetAny
.Lesen Sie diesen kurzen erklärenden Blog-Artikel über existentielle Typen in Scala ( BEARBEITEN : Dieser Link ist jetzt tot, ein Schnappschuss ist auf archive.org verfügbar. )
quelle
T[_]
(was ist ein besonderer Fall von existenziellem Gebrauch) undT[Any]
?T[_]
und istT[Any]
, da dies der Kern der Frage ist, warum überhauptT[_]
über verwendet wirdT[Any]
. Darüber hinaus erwähnte Sean Connolly in seiner Frage ausdrücklichList[_]
. Angesichts der Tatsache, dass diesList
tatsächlich kovariant ist, kann man sich fragen, ob es in diesem Fall wirklich einen Unterschied zwischenList[_]
und gibtList[Any]
.Any
als Obergrenze gilt, scheint es keinen Unterschied zwischenT[_]
und zu gebenT[Any]
. Wenn Sie jedoch habenT[+A <: B]
die Compiler seltsam genug nicht ableiten ,T[_ <: B]
ausT[_]
.