Reviving this thread with a another solution proposal. The previous discussion got a bit sidetracked, so it’s worth noting that the original problem remains unaddressed.
For reference, here's a version that makes the snowballing more apparent.
//> using scala 3.7.0
//> using option -language:experimental.modularity
trait Ordering:
type T
def compare(t1:T, t2: T): Int
class SetFunctor(tracked val ord: Ordering):
opaque type Set = List[ord.T]
def empty: Set = Nil
object SetFunctor:
type Of[Ord <: Ordering] =
SetFunctor { val ord: Ord }
class Foo(
tracked val ord: Ordering,
tracked val set: SetFunctor.Of[ord.type],
)
object Foo:
type Of[Ord <: Ordering, Set <: SetFunctor.Of[Ord]] =
Foo { val ord: Ord; val set: Set }
class Bar(
tracked val ord: Ordering,
tracked val set: SetFunctor.Of[ord.type],
tracked val foo: Foo.Of[ord.type, set.type],
):
summon[set.Set =:= foo.set.Set]
object Bar:
type Of[Ord <: Ordering, Set <: SetFunctor.Of[Ord], F <: Foo.Of[Ord, Set]] =
Bar { val ord: Ord; val set: Set; val foo: F }
class Baz(
tracked val ord: Ordering,
tracked val set: SetFunctor.Of[ord.type],
tracked val foo: Foo.Of[ord.type, set.type],
tracked val bar: Bar.Of[ord.type, set.type, foo.type],
):
summon[set.Set =:= foo.set.Set]
summon[set.Set =:= bar.set.Set]
summon[set.Set =:= bar.foo.set.Set]
object Baz:
type Of[Ord <: Ordering, Set <: SetFunctor.Of[Ord], F <: Foo.Of[Ord, Set], B <: Bar.Of[Ord, Set, F]] =
Baz { val ord: Ord; val set: Set; val foo: F; val bar: B }
object intOrdering extends Ordering:
type T = Int
def compare(t1: T, t2: T): Int = t1 - t2
val IntSet = SetFunctor(intOrdering)
val foo = Foo(intOrdering, IntSet)
val bar = Bar(intOrdering, IntSet, foo)
val baz = Baz(intOrdering, IntSet, foo, bar)
summon[IntSet.Set =:= foo.set.Set]
summon[IntSet.Set =:= bar.set.Set]
summon[IntSet.Set =:= bar.foo.set.Set]
summon[IntSet.Set =:= baz.set.Set]
summon[IntSet.Set =:= baz.foo.set.Set]
summon[IntSet.Set =:= baz.bar.set.Set]
summon[IntSet.Set =:= baz.bar.foo.set.Set]
The above code compiles, but notice the accretion of type parameters, instantiated to singleton types of previous value parameters, most pronounced in the definition of Baz
. A module depending on n other modules has up to n type parameters (here seen in the Foo.Of[...]
auxiliary types) and instantiates up to O(n2) type parameters of its dependencies to singleton types of its previous dependencies.
The task is to simplify this pattern to avoid the accretion of dependent type parameters.
Prerequisite
My new proposal depends on another old proposal, let’s call it value-dependent type aliases:
type Set(ord: Ordering) = List[ord.T]
(The linked proposal calls them context-dependent types, because the initial motivation there was to use them with implicit (contextual) values, and then generalized by @smarter in the first comment. I then never got to initiating a pre-SIP discussion on it.)
Solution
Introduce static
type members, which do not depend on object identity of its owner.
class SetFunctor(tracked val ord: Ordering):
static opaque type Set = List[ord.T]
The implementation would then move the definition of a static
type member to the companion object and put a (non-opaque) type alias in its original position. The rewrite of the above would be
class SetFunctor(tracked val ord: Ordering):
type Set = SetFunctor.Set(ord) // non-opaque here
object SetFunctor:
opaque type Set(ord: Ordering) = List[ord.T] // we keep the opaqueness here
Notice that we needed a value-dependent type alias to be able to extract Set
out of its owner class.
(Extraction would not be possible if the static
type member depended on the object identity of the owner class. In such case, compile-time error would be reported.)
In general, any relocated opaque static type member would be parameterized by the full list of type and value parameters of the owner class:
class MyFunctor[A](val b: B):
opaque type Collection[T] = <...>
would rewrite to
class MyFunctor[A](val b: B):
type Collection[T] = MyFunctor.Collection[A](b)[T]
object MyFunctor:
opaque type Collection <: [A] =>> (b: B) =>> [T] =>> Any =
[A] =>> (b: B) =>> [T] =>> <...>
Now, it would be nice if, in the IDE and error messages, the compiler referred to the type Collection[T]
as MyFunctor[A](b).Collection[T]
instead of MyFunctor.Collection[A](b)[T]
.
Why is it a solution?
As was noted by @alvae, the problem does not arise for non-opaque type aliases inside modules. By the rewrite, we managed to make the type member non-opaque inside the module (but still keeping it opaque in the companion).
Thoughts? Could it fly? Would you support the value-dependent type aliases proposal?