Imagine a pretty ordinary piece of code:
trait Template[X, +T] {
def m :T
}
trait Root[X] extends Template[X, Wrap[X]] {
override def m = new Wrap[X]
}
trait Sub[X] extends Root[X] with Template[X, Wrap2[X]] {
override def m = new Wrap2[X]
}
trait Wrap[X] extends Root[Option[X]]
trait Wrap2[X] extends Sub[X]
Root is a base class of a class hierarchy which happens to have method m the return type of which is occasionally narrowed by subclasses, as per Sub. An additional requirement is that I need that return type encoded somehow in the type of any C <: Root[_], so I can generalize over it. This is done through trait Template: I can in my implicits declare type parameters [X, T, W <: Template[X, T]] (forget for now problems with type inference here).
The problem is, the code above does not compile - the class graph is non-finitary. It is because:
Root[X] <: Template[X, Wrap[X]] <: Template[X, Root[Option[X]]]
For reasons unbeknownst to me, it is a good idea to expand the last type in the compiler by substituting Root[Option[X]] recursively with X. Now, if someone can explain why eager expanding of the subtype relation is done here, and not in Root[X] <: Template[X, Root[X]], for example, it would be great to know, but is not my main question here. Which is: how to refactor it?
I see two options:
1.
trait Template[X, +T[_]] {
def m :T[X]
}
This won't work, because the kind of the return type is not always the same: even adding a bound on a type parameter will break it.
- The second alternative, are of course, member types:
trait Root[X] {
type Result <: Wrap[X]
def m :Wrap[X]
}
trait Sub[X] extends Root[Option[X]] {
type Result <: Wrap2[X]
}
But member types come always with one huge problem: the only possible way of achieving covariance is by declaring the return type Result as an upper bound. However, in that case, I cannot implement method m within that class in any other way than throwing an exception, because Result is unknown in that place. If I want to keep the hierarchy open for extension, for every interface trait I have to have a separate implementation class, i.e.
class RootImpl[X] extends Root[X] {
override type Result = Wrap[X]
override def m = new Wrap[X]
}
class SubImpl[X] extends Sub[X] {
override type Result = Wrap2[X]
override def m = new Wrap2[X]
}
Even if I extract the above bodies to mix in traits, so I don't have to cut and paste the implementation in every Root subclass which would like to use the default implementation,
it is still quite inconvenient compared to having the implementation within the actual interface traits.
Is there anything I didn't think of?