Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala parameterized type problem with returning an instance of the same type

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 ....

The part that works

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.

The part that does not work

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.

My attempt to fix the problem

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?

like image 626
Madoc Avatar asked Feb 23 '11 23:02

Madoc


1 Answers

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 
%
like image 72
psp Avatar answered Sep 23 '22 02:09

psp