Let's consider:
trait Abstract {
type T
def get :T
def set(t :T) :Unit
}
class Concrete[X](var x :X) extends Abstract {
override type T = X
override def get :T = x
override def set(t :T) = x = t
}
class Generic[M <: Abstract](val m :M) {
def get :M#T = m.get
def set(t :M#T) :Unit = m.set(t)
def like[N <: Abstract](n :N) :Generic[N] = new Generic(n)
def trans[N <: Abstract](n :N) :N#T = like[N](n).get
}
Class Generic rightfully won't compile here because M#T can be literally any type in existence and not m.T. This can be 'fixed' in two ways:
def set(t :m.T) :Unit = ???
or
class Generic[M <: Abstract with Singleton]
First is unfeasible because in practice Generic might not even have an instance of M to use its member type (which happens in my real-life case), and passing around path dependent types in a compatible way between classes and methods is next to impossible. Here's one of the examples why path-dependent types are too strong (and a huge nuissance):
val c = new Concrete[Int](0)
val g = new Generic[c.type](c)
c.set(g.get)
The last line in the above example does not compile, even though g :Generic[c.type] and thus get :c.type#T which simplifies to c.T.
The second is somewhat better, but it still requires that every class with a M <: Abstract type parameter has an instance of M at instantiation and capturing m.T as a type parameter and passing everywhere a type bound Abstract { type T = Captured } is still a huge pain.
I would like to put a type bound on T specifying that only subclasses of Abstract which have a concrete definition of T are valid, which would make m.T =:= M#T for every m :M and neatly solve everything. So far, I haven't seen any way to do so.
I tried putting a type bound:
class Generic[M <: Abstract { type T = O } forSome { type O }]
which seemingly solved the issue of set, but fails with trans:
def like[N <: Abstract { type T = O } forSome { type O }](n :N) :Generic[N] = ???
def trans[N <: Abstract { type T = O } forSome { type O}](n :N) :N#T = ???
which seems to me like a clear type system deficiency. Additionally, it actually makes the situation worse in some cases, because it defines T early as some O(in class Generic) and won't unify it when additional constraints become available:
def narrow[N <: M { type T = Int }](n :N) :M#T = n.get
The above method will compile as part of class Generic[M <: Abstract], but not Generic[M <: Abstract { type T = O } forSome { type O}]. Replacing Abstract { type T = O } forSome { type O } with Concrete[_] yields very similar results. Both of these classes could be fixed by introducing a type parameter for T:
class Generic[M <: Abstract { type T = O }, O]
class Generic[M <: Concrete[_]]
Unfortunately, in several places I rely on two-argument type constructors such as
trait To[-X <: Abstract, +Y <: Abstract]
and use the infix notation X To Y as part of my dsl, so changing them to multi-argument, standard generic classes is not an option.
Is there a trick to get around this problem, namely to narrow down legal type parameters of a class so that their member type (or type parameter, I don't care) are both expressible as types and compatible with each other?. Think of M as some kind of factory of values of T and of Generic as its input data. I would like to parameterize Generic in such a way, that its instances are dedicated to concrete implementation classes of M. Do I have to write a macro for it?
I'm not sure exactly why you're saying that using a path dependent type will be annoying here, so maybe my answer will be completely off, sorry about that.
What's the problem with this?
You're saying you won't always have an instance
m: M, but can you provide a real example of exactly what you want to achieve?