-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Problem with type checking GADTs #3666
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Just verified that |
Related: #2675, #1870. Scalac: scala/bug#6680 |
Maybe I miss something here, it seems to me the compiler is justifiable in both of the two cases above. In the In the |
I've been plagued by this for a long time. case f @ Lambda(t, u) => should infer or if case e @ App(f, x) => should infer |
In both cases, GADT pattern matching requires synthesizing fresh type variables (with possible equality or subtyping constraints) and using them to refine the types of members. We don’t need those type variables to be made into type members as @ctongfei proposes, at least not to handle such examples, even though that would be sound. Maybe I can give a hand? |
@Blaisorblade If I recalled correctly, under Dotty, the encoding of generics for case class App[T, U](f: Exp[T => U], e: Exp[T]) extends Exp[U] would create type member T and U for the class |
Forgot to mention: as usual, such constraints would have to be sound even if we extend the case classes. Here all type parameters are invariant, so this should be easier since we can't write @ctongfei Using standard existentials (type variables) over type members seems as easy and more standard for GADTs (http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.119.8094&rep=rep1&type=pdf); Dotty already started doing this I think. What you say makes some sense and I'm familiar with that work, but in fact, Sec. 8 of that paper explains clearly Dotty actually uses the direct representation in Sec. 4, not the simple encoding in Sec. 3. Sec. 2.1 is about the encoding in core DOT; it also mentions dotty in "The problem is solved in DOT and dotty by a radical reduction", but I wouldn't take that too literally given the rest of the paper. And those type members don't seem to be actually available to the user (see below). Your proposal sounds plausible, and maybe it would be more expressive (I don't see evidence of that); it's also plausible that it's sound, but to know for sure some theorist would need to look at it for more than 5 minutes (there's one tricky question involved). Using type variables has fewer problems. > console
scala> trait Exp[T]
// defined trait Exp
scala> case class Var[T](name: String) extends Exp[T]
// defined case class Var
scala> Var[Int]("a").T
1 |Var[Int]("a").T
|^^^^^^^^^^^^^^^
|value `T` is not a member of Var[Int]
scala> val i : Var[Int]("a").T = 1
1 |val i : Var[Int]("a").T = 1
| ^^^
| ';' expected, but string literal found
scala> val v = Var[Int]("a")
val v: Var[Int] = Var(a)
scala> val i : v.T = 1
1 |val i : v.T = 1
| ^^^
|type T in class Var cannot be accessed as a member of Var[Int](v) from object rs$line$4. |
Sure @Blaisorblade , I haven't started working on this, I've assigned to you 👍 |
@liufengyun Implementing the ideas in dotty will take a while, but yeah I'd like to give it a serious try. (Assigning to me didn't quite work not sure why). Thanks! |
@Blaisorblade I'm not an expert on compiler design but I think that my proposal does make it more expressive. I'll show with the following example. I've been working on a typesafe deep learning system and the core part is backward differentiation, which is a traversal over a computation graph. Nodes in the computation graph, (i.e. symbolic expressions) are defined here as a GADT: To be brief, sealed trait Expr[X]
case class Input[X]() extends Expr[X]
case class Param[X](var value: X)(implicit val tag: Grad[X]) extends Expr[X]
case class Const[X](value: X)(implicit val tag: Type[X]) extends Expr[X]
case class App1[X, Y](op: Op1[X, Y], x: Expr[X]) extends Expr[Y]
case class App2[X1, X2, Y](op: Op2[X1, X2, Y], x1: Expr[X1], x2: Expr[X2]) extends Expr[Y]
case class App3[X1, X2, X3, Y](op: Op3[X1, X2, X3, Y], x1: Expr[X1], x2: Expr[X2], x3: Expr[X3]) extends Expr[Y] When doing backpropagation, we do pattern matching on symbolic expressions. // type of e: Expr[Y]
case e @ App2(o, x1, x2) => o match {
case o: Op2[e.Input1, e.Input2, _] => Naturally, the un-applied variables
However Scala 2 currently gives existential types ( I have to use another pattern matching on the second line to make it typecheck, without falling back to using the evil |
The current handling is indeed insufficient, I’ve run into similar issues years ago. One issue would be with writing those type variables explicitly (as you're doing in part) would require type patterns; it would probably non-linear ones (that is, patterns with repeated type variables), which aren’t supported yet. That seems still a smaller extension, though it motivates maybe looking a bit more into members. But I’d still prefer using the standard approach as far as possible. |
Can we use the same fresh type variables in different type patterns in Dotty? E.g. case e @ App2(o: Op2[e1, e2, Y], x1: Expr[e1], x2: Expr[e2]) => .... where fresh type variable |
@ctongfei In the second occurrence, I expect you’d need to use For instance, Dotty can’t (without manifests) check a match against
and ideally, if you write instead
we might want to either (kindly) figure out that |
Thanks @Blaisorblade for the explanation. Now I understand the issues behind these. I wonder why Dotty doesn't make the type member accessible to users. If not I would simply do something like case class App2[_X1, _X2, Y](f: Op2[_X1, _X2, Y], x1: _X1, x2: _X2) extends Expr[Y] {
type X1 = _X1
type X2 = _X2
} to force it to become visible. |
I remember people proposing But adding such type members by default instead of requiring |
Thanks. I'd like to see the |
I believe this issue has been fixed by #3918, but I'll leave it open until someone makes a PR to turn the example code into positive and negative testcases. |
The example showcases both scala#3666 and scala#1754.
So #3666 typechecks but causes tons of assertion failures in checks — for instance (http://dotty-ci.epfl.ch/lampepfl/dotty/3469/4):
|
Here's an interpreter for simply typed lambda calculus:
When we compile that, we get two mysterious warnings:
If we look under the hood we find that these stem from the fact that we somehow miss the right subtype constraints for the
Lambda
andApp
cases. I suspect something fundamental is wrong here. I don't have the time to look into it now, but maybe someone who is up-to-speed with GADT matching can?The text was updated successfully, but these errors were encountered: