I ran into another issue of scala's subtype relation being non transitive (https://github.com/scala/bug/issues/12164) and I start to think if there is some general work around possible. Long story short:
trait Thing { thisThing =>
type Super >: Self <: Thing {
type Super <: thisThing.Super
}
type Self <: Thing {
type Super = thisThing.Super
type Self = thisThing.Self
}
}
trait Wrapper extends Thing {
type SuperAdapted >: Adapted <: Thing
type Adapted <: Thing
}
trait Adapter[+T <: Thing] extends Wrapper {
val thing :T
type Super = Adapter[thing.Super]
type Self = Adapter[thing.Self]
type Adapted = thing.Self
}
type Universal[+T <: Thing] = Wrapper { type Adapted <: T }
And this does not compile:
val t :Thing
implicitly[Adapter[t.Super] <:< Universal[t.Super]]
Now I have T <: Thing { type General <: G }
but not Adapter[T] <:< Universal[G]
.
Is there a way to work around it? One would be of course to unitilize <:<#liftCo
, but it works only for values, not types, and prevents higher types from using Adapter[T]
and retaining a proper subtyping relation - basically now everywhere I'd have to switch back and forth using implicits and it's completely unmanagable. Can anyone think of an alternate definition of Universal
such that at least Adapter[t.Self] <:< Universal[t.Super]
and Adapter[t.Super] <:< Universal[t.Super]
, but preferably one which encompasses all wrappers with a.thing.Self <: T
for arbitrary T
(because I don't even have a T <: Thing
in the place where I need this subtyping to work). I don't know, type lambdas, braking it into partial type definitions to somehow manually enforce transitiveness?
What I eventually did is I reversed the dependency. Instead of
type Universal[+T <: Thing] = Adapter { type Adapted <: T }
(or similar), I added more member types to Thing
:
trait Thing { thisThing =>
/* as before */
type Adapt = Wrapper {
type SuperAdapted <: thisThing.Super
}
type AdaptSuper = Wrapper {
type SuperAdapted = thisThing.Super
type Adapted = thisThing.Self
}
}
where the type SuperAdapted
was ommitted from the original question in minimizing, but is required in my actual problem:
trait Adapter[+T <: Thing] {
/* as before */
type SuperAdapted = thing.Super
}
Now, instead of bounds [T <: Thing, A <: Universal[T]]
and passing [t.Super, Adapter[Super]]
I have [T <: Thing, A <: T#AdaptSuper]
(or [T <: Thing, A <: T#Adapt]
, depending on the use case). It is not as flexible or elegant as before, but works and fits my use cases. That is, until Scala 3...