Here is a simple example:
{ // dependent type in function
def dep[B](a: Any, bs: Seq[B]): Seq[(a.type, B)] = {
val result: Seq[(a.type, B)] = bs.map { b =>
(a: a.type) -> (b: B)
}
result
}
val ss = dep(3, Seq("a"))
val ss2: Seq[(3, String)] = ss
}
It works because a.type
automatically resolves to 3.type
at call site, despite that a
doesn't even have a deterministic path at definition site.
Works fine so far, but with a little twist, the call site expansion will no longer work:
{ // in case class
class Dep[B](a: Any, bs: Seq[B]) {
def result: Seq[(a.type, Any)] = {
val result: Seq[(a.type, B)] = bs.map { b =>
(a: a.type) -> (b: B)
}
result
}
}
object ss extends Dep(3, Seq("a"))
val ss2: Seq[(3, String)] = ss.result
}
/*
Found: Seq[((ss.a : Any), Any)]
Required: Seq[((3 : Int), String)]
Explanation
===========
Tree: ss.result
I tried to show that
Seq[((ss.a : Any), Any)]
conforms to
Seq[((3 : Int), String)]
but the comparison trace ended with `false`:
*/
Since Dep
is now a class constructor, the call site will stick to its member type definition instead of call site type. This caused a lot of confusion and violation of constructor function principles, Is there a way to augment the compiler to unify these 2 cases?
The closest I could come up with is to use an auxiliary constructor, but it only generates some inscrutable compiling error:
{ // in case class, as auxiliary constructor
case class Dep[A, B](a: A, bs: Seq[B]) {
def this[B](a: Any, bs: Seq[B]) = this[a.type, B](a, bs)
def result: Seq[(A, Any)] = {
val result: Seq[(A, B)] = bs.map { b =>
(a: A) -> (b: B)
}
result
}
}
}
/*
None of the overloaded alternatives of constructor Dep in class Dep with types
[A, B](): Dep[A, B]
[A, B](a: A, bs: Seq[B]): Dep[A, B]
match arguments (Null)
*/