Reputation: 103
Have some type T
and abstract class X[T]
, and what the main point is, for each concrete type T, if defined, there is only one subclass of X[T]
, for instance, IntX extends X[Int]
, which is the only subclass for X[T]
with T = Int
. That is, theoretically we have an injective mapping T -> X[T]
for some set of types.
Let's have a look on two simple definitions:
trait Context[T] {
type XType <: X[T]
}
abstract class X[T] extends Context[T] {
def plus1(that: XType): XType = ??? /* doesn't matter */
def plus2(that: XType): XType = that plus1 this
def sum(x1: XType, x2: XType): XType = x1.plus1(x2)
}
Here we see our X[T]
with some methods. To have correct final types in concrete inherited subclasses, I use XType
as type of subclass inherited from X[T]
. For instance, like this one:
trait IntContext extends Context[Int] {
type XType = IntX
}
class IntX extends X[Int] with IntContext
And then method IntX.plus1
takes IntX
and returns IntX
, not X[Int]
, so this is a brief explanation of rather abstract example. Context
is used to contain all information about types and fabric constructors, related to each used type T. Well, there is more meaningful example of Context
, just to understand things correctly:
trait Context[V <: ArithmType[V]] { /* V such as Int, Double */
type Point <: AbstractPoint[V]
type Line <: AbstractLine[V]
type Rect <: AbstractRect[V]
...
def newPoint(x: V, y: V): Point
def newLine(v1: Point, v2: Point): Line
def newRect(p: Point, w: V, h: V): Rect
...
def pointCompanion: AbstractPoint.Companion[V]
def lineCompanion: AbstractLine.Companion[V]
def rectCompanion: AbstractRect.Companion[V]
...
}
The problem is:
The code with X[T]
will not compile. Of course, if we have a look on two last methods, we will get the following errors:
Type mismatch, expected: that.XType, actual: X[T]
Type mismatch, expected: x1.XType, actual: X.this.XType
We see that compiler treats own type of each instance of XType variable as different one from each other. And this is right, of course, but the thing compiler doesn't know is the injectivity of our inheritance: for fixed type T
all XType
type values are the same.
How could I implement such a logic to overpass this?
I've designed one solution, but it's rather dirty. Rewrite the code:
trait Context[T] {
type XType <: X[T]
implicit def cast(x: X[T]): XType = x.asInstanceOf(XType)
}
abstract class X[T] extends Context[T] {
def plus1(that: XType): XType = ??? /* doesn't matter */
def plus2(that: XType): XType = that plus1 that.cast(this)
def sum(x1: XType, x2: XType): XType = x1 plus1 x1.cast(x2)
}
Without implicit casting the methods will be like:
def plus2(that: XType): XType = cast(that plus1 that.cast(this))
def sum(x1: XType, x2: XType): XType = cast(x1 plus1 x1.cast(x2))
asInstanceOf
-casting will not fail as we know about our constraint on injectivity. One can use pattern matching, but this are details.
The main disadvantage of this solution is the need of class code refactoring: we put some cluttering casts in out business logic parts.
Does this solution have the right to be used? What ideas do You have?
Edit: Is there a way to use Aux
technique in this case?
Upvotes: 1
Views: 62
Reputation: 44918
If for each T
there is always only one concrete subclass Repr <: X[T]
, then this class Repr
will itself know that every other X[T]
must be Repr
. So, just give the type Repr
as an argument to X
, so it can use it in all plusXYZ
-method declarations:
trait Context[T, Repr <: X[T, Repr]]
abstract class X[T, Repr <: X[T, Repr]] extends Context[T, Repr] {
def plus1(that: Repr): Repr = ??? /* doesn't matter */
def plus2(that: Repr): Repr = that plus1 that
def sum(x1: Repr, x2: Repr): Repr = x1 plus1 x2
}
class IntX extends X[Int, IntX]
While this works, a word of warning: all those circular f-bounded-polymorphism stunts tend to become rather nasty rather quickly. Typeclasses tend to compose much nicer.
And by the way: I'm not sure what the function of Context
in the above code snippet is. It doesn't seem to do anything.
Upvotes: 1