scalarefinement-type

Is there a pattern/trick to enforce '<:' when defining type aliases with '='


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?


Solution

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