In the following, I will present only very reduced versions of my Scala code. Just enough to show the problem. Unnecessary blocks of code will be reduced to ...
.
I have created a vector library (that is, for modeling mathematical vectors, not vectors in the sense of scala.collection.Vector
). The basic trait looks like this:
trait Vec[C] extends Product {
def -(o:Vec[C]):Vec[C] = ...
...
}
I have created numerous subtypes for specific vectors, like Vec2
for two-dimensional vectors, or Vec2Int
specialized for two-dimensional Int
vectors.
The subtypes narrow the return types of some operations. For example, subtracting a Vec2Int
from another vector will not return a generic Vec[Int]
, but the more specific Vec2Int
.
Additionally, I have declared those methods in very specific subtypes like Vec2Int
as final
, thereby allowing the compiler to elect those methods for inlining.
This works very well, and I have created a fast and usable library for vector calculations.
Building on top of that, I now want to create a set of types to model basic geometrical shapes. The basic shape trait looks like this:
trait Shape[C, V <: Vec[C]] extends (V=>Boolean) {
def boundingBox:Box[C,V]
}
Where Box
would be a subtype of Shape
, modeling an n-dimensional box.
Now, I tried to define box:
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
}
The diagonal
method does not compile, because the method Vec.-
returns Vec[C]
, not V
.
Of course, I could make diagonal
return a Vec[C]
, but this would be unacceptable in many ways. For once, I would lose the compiler optimization for specific Vec
subtypes. Also, When you for example have a box described by two two-dimensional Float
vectors (Vec2Float
), it makes a lot of sense to assume that the diagonal is also a Vec2Float
. I do not want to lose that information.
Following the example of the Scala collection hierarchy, I introduced a type VecLike
:
trait VecLike[C, +This <: VecLike[C,This] with Vec[C]] {
def -(o:Vec[C]):This
...
}
and I made Vec
extend it:
trait Vec[C] extends Product with VecLike[C, Vec[C]] ...
(I would then go on to create more specific subtypes of VecLike
, like Vec2Like
or Vec3Like
, to accompany my hierarchy of Vec
types.)
Now, the new definition for Shape
and Box
looks like this:
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
}
Still, the compiler complains:
Error: type mismatch;
found: Vec[C]
required: V
This confuses me. The type VecLike
clearly returns This
in the minus method, which translates to the type parameter V
of the Box
type. I can see that the minus method of Vec
still returns Vec[C]
, but why cannot the compiler at this point use the return type of VecLike
's minus method?
How can I fix this problem?
My advice is to work much less hard on omitting the code you think is irrelevant, and just show the code. It's really amazing how often people manage to remove the part which matters. The mantra is "if you don't know why it doesn't work, then you don't know what is relevant." This is very serious, genuine advice: I can help you in five seconds if you give me code which would compile except for the thing you don't understand, or I can help you in five minutes if I have to reconstruct all the pieces you left out. Guess which one happens more often.
On to the code. It compiles exactly as given, after I make guesses about how the bits from the first attempt fill into the second attempt. (This "guessing" phase is another good reason to show the code up front.)
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
%
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With