/ / Problem mit parametrisiertem Scala-Typ bei Rückgabe einer Instanz des gleichen Typs - Generics, Scala, Scala-2.8

Scala-parametrisiertes Typproblem bei der Rückgabe einer Instanz desselben Typs - generics, scala, scala-2.8

Im Folgenden stelle ich nur sehr reduzierte Versionen meines Scala-Codes vor. Gerade genug, um das Problem aufzuzeigen. Unnötige Codeblöcke werden auf . reduziert ....

Der Teil, der funktioniert

Ich habe eine Vektorbibliothek erstellt (also zur Modellierung mathematischer Vektoren, nicht Vektoren im Sinne von scala.collection.Vector). Das Grundmerkmal sieht so aus:

trait Vec[C] extends Product {
def -(o:Vec[C]):Vec[C] = ...
...
}

Ich habe zahlreiche Untertypen für bestimmte Vektoren erstellt, wie zum Beispiel Vec2 für zweidimensionale Vektoren, oder Vec2Int spezialisiert auf zweidimensionale Int Vektoren.

Die Untertypen grenzen die Rückgabetypen einiger Operationen ein. Subtrahieren zum Beispiel a Vec2Int von einem anderen Vektor wird kein Generic zurückgeben Vec[Int], aber umso spezifischer Vec2Int.

Darüber hinaus habe ich diese Methoden in sehr spezifischen Untertypen wie deklariert Vec2Int wie final, wodurch der Compiler diese Methoden für das Inlining auswählen kann.

Das funktioniert sehr gut und ich habe eine schnelle und brauchbare Bibliothek für Vektorberechnungen erstellt.

Darauf aufbauend möchte ich nun eine Reihe von Typen erstellen, um grundlegende geometrische Formen zu modellieren. Das grundlegende Formmerkmal sieht wie folgt aus:

trait Shape[C, V <: Vec[C]] extends (V=>Boolean) {
def boundingBox:Box[C,V]
}

Woher Box wäre ein Untertyp von Shape, Modellieren einer n-dimensionalen Box.

Der Teil, der nicht funktioniert

Jetzt habe ich versucht, Box zu definieren:

trait Box[C, V <: Vec[C]] extends Shape[C,V] {
def lowCorner:V
def highCorner:V
def boundingBox = this
def diagonal:V = highCorner - lowCorner // does not compile
}

Das diagonal Methode kompiliert nicht, weil die Methode Vec.- kehrt zurück Vec[C]nicht V.

Natürlich könnte ich machen diagonal zurückgeben Vec[C], aber das wäre in vielerlei Hinsicht inakzeptabel. Ausnahmsweise würde ich die Compiler-Optimierung für bestimmte verlieren Vec Unterarten. Auch, wenn Sie zum Beispiel eine Box haben, die durch zwei zweidimensionale . beschrieben wird Float Vektoren (Vec2Float), macht es Sinn anzunehmen, dass die Diagonale auch a Vec2Float. Ich möchte diese Informationen nicht verlieren.

Mein Versuch das Problem zu beheben

Dem Beispiel der Scala-Sammlungshierarchie folgend, habe ich einen Typ eingeführt VecLike:

trait VecLike[C, +This <: VecLike[C,This] with Vec[C]] {
def -(o:Vec[C]):This
...
}

und ich habe gemacht Vec erweitere es:

trait Vec[C] extends Product with VecLike[C, Vec[C]] ...

(Ich würde dann weitergehen, um spezifischere Untertypen von . zu erstellen VecLike, mögen Vec2Like oder Vec3Like, um meine Hierarchie von . zu begleiten Vec Typen.)

Jetzt die neue Definition für Shape und Box sieht aus wie das:

trait Shape[C, V <: VecLike[C,V] with Vec[C]] ...

trait Box[C, V <: VecLike[C,V] with Vec[C]] extends Shape[C,V] {
...
def diagonal:V = highCorner - lowCorner
}

Trotzdem beschwert sich der Compiler:

Error: type mismatch;
found: Vec[C]
required: V

Das verwirrt mich. Der Typ VecLike eindeutig zurück This in der Minus-Methode, die in den Typparameter übersetzt wird V des Box Art. Ich kann sehen, dass die Minus-Methode von Vec kommt immer noch zurück Vec[C], aber warum kann der Compiler an dieser Stelle nicht den Rückgabetyp von verwenden? VecLike"s-Minus-Methode?

Wie kann ich dieses Problem beheben?

Antworten:

6 für die Antwort № 1

Mein Rat ist, viel weniger hart am Weglassen zu arbeitender Code, den Sie für irrelevant halten, und zeigen Sie den Code einfach an. Es ist wirklich erstaunlich, wie oft es Menschen gelingt, den wichtigen Teil zu entfernen. Das Mantra lautet: "Wenn Sie nicht wissen, warum es nicht funktioniert, wissen Sie nicht, was relevant ist." Dies ist ein sehr ernster, echter Rat: Ich kann Ihnen in fünf Sekunden helfen, wenn Sie mir Code geben, der bis auf das, was Sie nicht verstehen, kompilieren würde, oder ich kann Ihnen in fünf Minuten helfen, wenn ich alle Teile rekonstruieren muss, die Sie haben Ratet mal, was häufiger vorkommt.

Weiter zum Code.Es kompiliert genau wie angegeben, nachdem ich Vermutungen angestellt habe, wie sich die Bits aus dem ersten Versuch in den zweiten Versuch füllen. (Diese Phase des „Ratens“ ist ein weiterer guter Grund, den Code im Voraus zu zeigen.)

trait VecLike[C, +This <: VecLike[C, This] with Vec[C]] {
def -(o: Vec[C]): This
}

trait Vec[C] extends Product with VecLike[C, Vec[C]] { }

trait Shape[C, V <: VecLike[C,V] with Vec[C]] { }

trait Box[C, V <: VecLike[C,V] with Vec[C]] extends Shape[C, V] {
def lowCorner: V
def highCorner: V
def boundingBox = this
def diagonal: V = highCorner - lowCorner
}

% scalac281 a.scala
%