Frage

diese Frage kann jemand erklären, die folgende in Scala:

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 (es kompiliert, wenn ich verwenden T ). Aber wie schreibt man eigentlich eine Klasse, die ohne Rückgriff auf der Schaffung die Sache in seinem Typparameter kovariant ist unparametrized ? Wie kann ich sicherstellen, dass die folgenden kann nur mit einer Instanz von erstellt werden T

class Slot[+T] (var some: Object){    
  def get() = { some.asInstanceOf[T] }
}

Bearbeiten - jetzt dies machte sich auf die folgenden:

abstract class _Slot[+T, V <: T] (var some: V) {
    def getT() = { some }
}

Das ist alles gut, aber ich habe jetzt zwei Typparametern, wo ich nur einen wollen. Ich werde die Frage erneut stellen also:

Wie kann ich schreibe eine unveränderlich Slot Klasse, die ist covariant in seiner Art?

EDIT 2 : Duh! Ich benutzte var und nicht val. Im Folgenden ist das, was ich wollte:

class Slot[+T] (val some: T) { 
}
War es hilfreich?

Lösung

generisch, a kovarianten Typ-Parameter ist eine, die nach unten zu variieren, erlaubt ist, als die Klasse subtyped wird (alternativ variiert mit Subtypisierung, daher die „co-“ prefix). Genauer gesagt:

trait List[+A]

List[Int] ist ein Subtyp von List[AnyVal] weil Int ein Subtyp von AnyVal ist. Dies bedeutet, dass Sie eine Instanz von List[Int] liefern können, wenn ein Wert vom Typ List[AnyVal] erwartet wird. Das ist wirklich eine sehr intuitive Art und Weise für Generika zu arbeiten, aber es stellt sich heraus, dass es unvernünftig ist (bricht das Typ-System), wenn in Gegenwart von änderbaren Daten verwendet. Aus diesem Grund Generika sind invariant in Java. Kurzes Beispiel unsoundness mit Hilfe von Java-Arrays (die fälschlicherweise covariant):

Object[] arr = new Integer[1];
arr[0] = "Hello, there!";

Wir haben gerade einen Wert vom Typ String zu einem Array vom Typ Integer[] zugeordnet. Aus Gründen, die offensichtlich sein sollte, dann ist dies eine schlechte Nachricht. Java-Typ-System ermöglicht tatsächlich die bei der Kompilierung. Die JVM wird „helfend“ ein ArrayStoreException zur Laufzeit werfen. Scala Typ-System verhindert, dass dieses Problem, weil die Typ-Parameter auf der Array Klasse ist unveränderlich (Erklärung [A] statt [+A]).

Beachten Sie, dass es eine andere Art von Varianz als bekannt ist Kontra . Dies ist sehr wichtig, da es erklärt, warum Kovarianz einige Probleme verursachen kann. Kontra ist buchstäblich das Gegenteil von Kovarianz: Parameter variieren nach oben mit Subtyping. Es ist viel weniger häufig zum Teil, weil es so kontraintuitiv ist, obwohl es eine sehr wichtige Anwendung hat. Funktionen

trait Function1[-P, +R] {
  def apply(p: P): R
}

Beachten Sie die " - " Varianz Anmerkung auf dem P Typ-Parameter. Diese Erklärung als Ganzes bedeutet, dass Function1 in P und covariant in R kontra ist. So können wir die folgenden Axiome ableiten:

T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']

Beachten Sie, dass T1' muss einen Subtyp (oder die gleiche Art) von T1, wohingegen das Gegenteil für T2 und T2' ist. In Englisch, kann dies als Folgendes gelesen werden:

  

Eine Funktion A ist ein Subtyp einer anderen Funktion B , wenn der Parameter Art von A ist ein übergeordneter Typ des Parameters Art von B , während der Rückgabetyp von A ist ein Subtyp des Rückgabetypen von B .

Der Grund für diese Regel als eine Übung für den Leser überlassen. (Hinweis: Man denke über verschiedene Fälle als Funktionen subtyped sind, wie mein Array Beispiel von oben)

Mit Ihrem neu gewonnenes Wissen von Ko- und Kontra, sollten Sie in der Lage sein, zu sehen, warum das folgende Beispiel nicht kompiliert:

trait List[+A] {
  def cons(hd: A): List[A]
}

Das Problem ist, dass A covariant ist, während die cons Funktionsparameter dieser Art erwartet invariant zu sein. Somit variiert A die falsche Richtung. Interessanterweise können wir dieses Problem lösen, indem List kontra in A zu machen, aber dann wäre der Rückgabetyp List[A] ungültig sein, da die cons Funktion dessen Rückgabetyp covariant sein erwartet.

Unsere nur zwei Möglichkeiten hier sind, um a) machen A invariant, die schönen, intuitive Subtypisierung Eigenschaften von Kovarianz zu verlieren, oder b) einem lokalen Typ-Parameter auf die cons Methode hinzufügen, die A als untere Grenze definiert:

def cons[B >: A](v: B): List[B]

Dies ist nun gültig. Sie können sich vorstellen, dass A nach unten variiert, aber B ist in der Lage in Bezug nach oben zu variieren, um zu A seit A seiner unteren Grenze ist. Mit dieser Methode Erklärung können wir haben A seinen covariant und alles klappt.

Beachten Sie, dass this Trick funktioniert nur, wenn wir eine Instanz von List zurück, die auf der weniger spezifischen Typ B spezialisiert. Wenn Sie versuchen, List wandelbar zu machen, Dinge brechen, da Sie am Ende versucht, Werte vom Typ B auf eine Variable vom Typ A zuweisen, die vom Compiler nicht zulässig ist. Jedes Mal, wenn Sie Veränderlichkeit haben, benötigen Sie einen Mutator irgendeiner Art zu haben, das ein Verfahren Parameter eines bestimmten Typs erfordert, die (zusammen mit dem Accessor) Invarianz impliziert. Kovarianz arbeitet mit unveränderlichen Daten, da der einzig mögliche Betrieb ein Accessor, das einen kovariante Rückgabetyp angegeben werden kann.

Andere Tipps

@ Daniel hat es sehr gut erklärt. Aber um es kurz zu erklären, wenn es erlaubt wurde:

  class Slot[+T](var some: T) {
    def get: T = some   
  }

  val slot: Slot[Dog] = new Slot[Dog](new Dog)   
  val slot2: Slot[Animal] = slot  //because of co-variance 
  slot2.some = new Animal   //legal as some is a var
  slot.get ??

slot.get wird dann einen Fehler zur Laufzeit werfen, wie es bei der Umwandlung eines Animal erfolglos war Dog (duh!).

Generell Veränderlichkeit geht nicht gut mit Kovarianz und Kontravarianz. Das ist der Grund, warum alle Java Sammlungen unveränderlich sind.

Siehe Scala mit gutem Beispiel Seite 57+ für eine vollständige Diskussion dieser.

Wenn ich Ihren Kommentar richtig zu verstehen, müssen Sie den Durchgang noch einmal zu lesen an der Unterseite der Seite beginnend 56 (im Grunde, was ich denke, Sie fordern ist nicht typsicher ohne Laufzeitprüfungen, die scala doesn‘ t tun, so dass Sie kein Glück). Übersetzen von ihrem Beispiel Ihr Konstrukt zu verwenden:

val x = new Slot[String]("test") // Make a slot
val y: Slot[Any] = x             // Ok, 'cause String is a subtype of Any
y.set(new Rational(1, 2))        // Works, but now x.get() will blow up 

Wenn Sie das Gefühl verstehe ich Ihre Frage nicht (eine eindeutige Möglichkeit), versuchen, mehr Erklärung / Kontext der Problembeschreibung Hinzufügen und ich werde versuchen Sie es erneut.

Als Antwort auf Ihre bearbeiten. Immutable Slots sind eine ganz andere Situation ... * smile * Ich hoffe, dass das obige Beispiel hilft

Sie müssen eine untere Grenze für den Parameter anzuwenden. Ich bin eine harte Zeit, die Syntax zu erinnern, aber ich denke, es ist so etwas wie folgt aussehen:

class Slot[+T, V <: T](var some: V) {
  //blah
}

Die Scala-by-Beispiel ist ein bisschen schwer zu verstehen, wären ein paar konkreten Beispiele geholfen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top