Description
In #4069 @smarter suggests GADT constraints might require unification:
We end up with GADT constraints foo >:> S and S >:> foo but we don't have a mechanism to unify GADT constraints, so we end up with a loop in subtyping checks. We could add a special case to handle simple unifications like this but it seems like to be completely general we really need to reuse all of ConstraintHandling to deal with GADT constraints too.
So here's an unification-based use of GADTs: length-indexed vectors, which indeed doesn't typecheck. I also initially hoped to use erased
for TNatSum
(which would maybe work in Agda), but that'd be a request for enhancement in fact (and not sure it's one we should grant).
I think this is still a relatively simple testcase, and I'm not sure it requires ConstraintHandling in the way @smarter meant, but I hope it is helpful. Before fixing this, I'd suggest a review the literature to find a comprehensive design (and I volunteer to take a look).
"Principal Type Inference for GADTs", from 2016, is a relatively recent reference (and pretty original) with a decent review of related work; I think "Complete and Decidable Type Inference for GADTs" and "OutsideIn(x) modular type inference with local assumptions" describe GHC's basic approach, while "GADTs Meet Their Match: Pattern-matching Warnings That Account for GADTs, Guards, and Laziness" describe GHC extensions for warnings.
object NatsVects {
sealed trait TNat
case class TZero() extends TNat
case class TSucc[N <: TNat] extends TNat
object TNatSum {
sealed trait TSum[M, N, R]
case class TSumZero[N]() extends TSum[TZero, N, N]
case class TSumM[M <: TNat, N, R <: TNat](sum: TSum[M, N, R]) extends TSum[TSucc[M], N, TSucc[R]]
}
import TNatSum._
implicit def tSumZero[N]: TSum[TZero, N, N] =
TSumZero()
//new TSum[TZero, N, N]() {}
implicit def tSumM[M <: TNat, N, R <: TNat](implicit sum: TSum[M, N, R]): TSum[TSucc[M], N, TSucc[R]] =
TSumM(sum)
//new TSum[TSucc[M], N, TSucc[R]] {}
sealed trait Vec[+T, N <: TNat]
case object VNil extends Vec[Nothing, TZero] // fails but in refchecks
case class VCons[T, N <: TNat](x: T, xs: Vec[T, N]) extends Vec[T, TSucc[N]]
implicit class vecOps[T, M <: TNat]($this: Vec[T, M]) extends AnyVal {
def append[N <: TNat, R <: TNat](that: Vec[T, N])(implicit tsum: TSum[M, N, R]): Vec[T, R] = {
// tsum match {
// case _: TSumZero[N] => // Here N = R
// $this match {
// case VNil =>
// //that
// that.asInstanceOf[Vec[T, R]]
// case VCons(x, xs) =>
// ???
// }
// case TSumM(sum) =>
// ???
// }
$this match {
case VNil => // M = TZero
tsum match {
case _: TSumZero[TZero] =>
//that
that.asInstanceOf[Vec[T, R]]
case _: TSumM[_, _, _] => // Impossible, this forces M = TSucc[M1]
???
}
case vxs: VCons[T, m1] => // M = TSucc[m1], xs: Vec[T, m1]
val x = vxs.x
val xs = vxs.xs
//case VCons(x, xs) =>
tsum match {
case _: TSumZero[TZero] => // impossible, since this forces M = TZero.
???
// fails
// case tsum1: TSumM[`m1`, n, r] => // M = TSucc[m1], R = TSucc[r]
// implicit val tsum2 = tsum1.sum
// val appended = xs append that
// VCons(x, appended) // Vec[T, TSucc[r]]
//works
case tsum1: TSumM[`m1`, N, r] => // M = TSucc[m1], R = TSucc[r]
implicit val tsum2 = tsum1.sum
// I should be able to return:
//VCons(x, xs append that) // Vec[T, TSucc[r]] = Vec[T, R]
val vxs1 = xs append that
val vxs2 = VCons(x, vxs1)
// vxs1
// [error] -- [E007] Type Mismatch Error: /Users/pgiarrusso/git/dotty-example-project/src/main/scala/playground/gadtVect.scala:65:14
// [error] 65 | vxs1
// [error] | ^^^^
// [error] | found: NatsVects.Vec[T, r](vxs1)
// [error] | required: NatsVects.Vec[T, R]
// [error] |
// [error] | where: r is a type in method append with bounds <: NatsVects.TNat
// vxs2
// [error] -- [E007] Type Mismatch Error: /Users/pgiarrusso/git/dotty-example-project/src/main/scala/playground/gadtVect.scala:71:14
// [error] 71 | vxs2
// [error] | ^^^^
// [error] | found: NatsVects.VCons[T, r](vxs2)
// [error] | required: NatsVects.Vec[T, R]
// [error] |
// [error] | where: r is a type in method append with bounds <: NatsVects.TNat
vxs2.asInstanceOf // sorry this works
}
}
}
}
}