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 № 1Mein 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
%