Kann jemand im Anschluss an diese Frage in Scala Folgendes erklären:
class Slot[+T] (var some: T) {
// DOES NOT COMPILE
// "COVARIANT parameter in CONTRAVARIANT position"
}
Ich verstehe den Unterschied zwischen +T
und T
in der Typdeklaration (sie wird kompiliert, wenn ich sie verwende T
). Aber wie schreibt man dann tatsächlich eine Klasse, deren Typparameter kovariant ist, ohne das nicht parametrisierte Objekt zu erstellen ? Wie kann ich sicherstellen, dass Folgendes nur mit einer Instanz von erstellt werden kann T
?
class Slot[+T] (var some: Object){
def get() = { some.asInstanceOf[T] }
}
BEARBEITEN - jetzt auf Folgendes zurückzuführen:
abstract class _Slot[+T, V <: T] (var some: V) {
def getT() = { some }
}
Das ist alles gut, aber ich habe jetzt zwei Typparameter, von denen ich nur einen möchte. Ich werde die Frage folgendermaßen erneut stellen:
Wie kann ich eine unveränderliche Slot
Klasse schreiben, die in ihrem Typ kovariant ist ?
EDIT 2 : Duh! Ich habe benutzt var
und nicht val
. Folgendes wollte ich:
class Slot[+T] (val some: T) {
}
quelle
var
es einstellbar ist, währendval
es nicht ist. Es ist der gleiche Grund, warum Scalas unveränderliche Sammlungen kovariant sind, die veränderlichen jedoch nicht.Antworten:
Im Allgemeinen ist ein kovarianter Typparameter einer, der während der Subtypisierung der Klasse nach unten variieren darf (alternativ mit der Subtypisierung variieren, daher das Präfix "co-"). Konkreter:
List[Int]
ist ein Subtyp vonList[AnyVal]
weilInt
ist ein Subtyp vonAnyVal
. Dies bedeutet, dass Sie eine Instanz angeben können, in derList[Int]
ein Wert vom TypList[AnyVal]
erwartet wird. Dies ist wirklich eine sehr intuitive Art und Weise, wie Generika arbeiten, aber es stellt sich heraus, dass es nicht gesund ist (das Typensystem bricht), wenn es in Gegenwart veränderlicher Daten verwendet wird. Aus diesem Grund sind Generika in Java unveränderlich. Kurzes Beispiel für Unklarheiten bei der Verwendung von Java-Arrays (die fälschlicherweise kovariant sind):Wir haben gerade
String
einem Array vom Typ einen Wert vom Typ zugewiesenInteger[]
. Aus Gründen, die offensichtlich sein sollten, sind dies schlechte Nachrichten. Das Java-Typsystem ermöglicht dies tatsächlich zur Kompilierungszeit. Die JVM wirdArrayStoreException
zur Laufzeit "hilfreich" einen werfen . Das Typensystem von Scala verhindert dieses Problem, da der Typparameter für dieArray
Klasse unveränderlich ist (Deklaration ist[A]
eher als[+A]
).Beachten Sie, dass es eine andere Art von Varianz gibt, die als Kontravarianz bekannt ist . Dies ist sehr wichtig, da es erklärt, warum Kovarianz einige Probleme verursachen kann. Kontravarianz ist buchstäblich das Gegenteil von Kovarianz: Die Parameter variieren mit der Subtypisierung nach oben . Es ist teilweise viel seltener, weil es so kontraintuitiv ist, obwohl es eine sehr wichtige Anwendung hat: Funktionen.
Beachten Sie die Varianzanmerkung " - " für den
P
Typparameter. Diese Erklärung als Ganzes bedeutet, dass sieFunction1
kontravariantP
und kovariant istR
. Somit können wir die folgenden Axiome ableiten:Beachten Sie, dass
T1'
dies ein Subtyp (oder derselbe Typ) von sein mussT1
, während es fürT2
und das Gegenteil istT2'
. Auf Englisch kann dies wie folgt gelesen werden:Der Grund für diese Regel bleibt dem Leser als Übung überlassen (Hinweis: Denken Sie an verschiedene Fälle, wenn Funktionen subtypisiert sind, wie in meinem Array-Beispiel von oben).
Mit Ihrem neu gewonnenen Wissen über Co- und Kontravarianz sollten Sie erkennen können, warum das folgende Beispiel nicht kompiliert wird:
Das Problem ist, dass
A
es kovariant ist, während diecons
Funktion erwartet, dass ihr Typparameter invariant ist . SomitA
ändert sich die falsche Richtung. Interessanterweise könnten wir dieses Problem lösen, indem wirList
Contravariant in setzenA
, aber dann wäre der RückgabetypList[A]
ungültig, da diecons
Funktion erwartet, dass sein Rückgabetyp kovariant ist .Unsere einzigen beiden Möglichkeiten sind: a)
A
Invariante zu machen , die netten, intuitiven Subtypisierungseigenschaften der Kovarianz zu verlieren, oder b) dercons
Methode, dieA
als Untergrenze definiert , einen lokalen Typparameter hinzuzufügen :Dies ist jetzt gültig. Sie können sich vorstellen , dass
A
nach unten variiert, aber inB
der Lage , sich nach oben zu variieren bezüglichA
daA
ist die Untergrenze. Mit dieser Methodendeklaration können wirA
kovariant sein und alles funktioniert.Beachten Sie, dass dieser Trick nur funktioniert, wenn wir eine Instanz zurückgeben,
List
die auf den weniger spezifischen Typ spezialisiert istB
. Wenn Sie versuchen,List
veränderlich zu machen , brechen die Dinge zusammen, da Sie am Ende versuchenB
, einer Variablen vom Typ Werte vom Typ zuzuweisenA
, die vom Compiler nicht zugelassen werden. Wenn Sie veränderlich sind, benötigen Sie einen Mutator, für den ein Methodenparameter eines bestimmten Typs erforderlich ist, der (zusammen mit dem Accessor) eine Invarianz impliziert. Die Kovarianz arbeitet mit unveränderlichen Daten, da die einzig mögliche Operation ein Accessor ist, dem ein kovarianter Rückgabetyp zugewiesen werden kann.quelle
trait Animal
,trait Cow extends Animal
,def iNeedACowHerder(herder: Cow => Unit, c: Cow) = herder(c)
unddef iNeedAnAnimalHerder(herder: Animal => Unit, a: Animal) = herder(a)
. DanniNeedACowHerder({ a: Animal => println("I can herd any animal, including cows") }, new Cow {})
ist es okay, da unser Tierhirte Kühe hüten kann, aberiNeedAnAnimalHerder({ c: Cow => println("I can herd only cows, not any animal") }, new Animal {})
einen Kompilierungsfehler gibt, da unser Kuhhirte nicht alle Tiere hüten kann.@ Daniel hat es sehr gut erklärt. Aber um es kurz zu erklären, wenn es erlaubt war:
slot.get
wird dann zur Laufzeit einen Fehler auslösen, da die Konvertierung vonAnimal
nachDog
(duh!) nicht erfolgreich war.Im Allgemeinen passt die Mutabilität nicht gut zu Co-Varianz und Contra-Varianz. Aus diesem Grund sind alle Java-Sammlungen unveränderlich.
quelle
Eine ausführliche Beschreibung finden Sie unter Scala anhand eines Beispiels , Seite 57+.
Wenn ich Ihren Kommentar richtig verstehe, müssen Sie die Passage ab Ende 56 erneut lesen (im Grunde genommen ist das, was Sie verlangen, ohne Laufzeitprüfungen nicht typsicher, was Scala nicht tut. du hast also kein Glück). Übersetzen Sie ihr Beispiel, um Ihr Konstrukt zu verwenden:
Wenn Sie der Meinung sind, dass ich Ihre Frage nicht verstehe (eine eindeutige Möglichkeit), fügen Sie der Problembeschreibung weitere Erklärungen / Zusammenhänge hinzu, und ich werde es erneut versuchen.
Als Antwort auf Ihre Bearbeitung: Unveränderliche Slots sind eine ganz andere Situation ... * lächeln * Ich hoffe, das obige Beispiel hat geholfen.
quelle
Sie müssen eine Untergrenze für den Parameter anwenden. Es fällt mir schwer, mich an die Syntax zu erinnern, aber ich denke, sie würde ungefähr so aussehen:
Die Scala-by-Example ist etwas schwer zu verstehen, ein paar konkrete Beispiele hätten geholfen.
quelle