Skip to content

Commit c393efe

Browse files
committed
Rewritten GADT section
1 parent 61e7829 commit c393efe

File tree

1 file changed

+56
-43
lines changed

1 file changed

+56
-43
lines changed

docs/blog/_posts/2018-03-05-seventh-dotty-milestone-release.md

+56-43
Original file line numberDiff line numberDiff line change
@@ -116,59 +116,72 @@ We also improved the `find references` functionality. It is more robust and much
116116

117117
Try it out in [Visual Studio Code](http://dotty.epfl.ch/docs/usage/ide-support.html)!
118118

119-
### Improvements in GADT type inference
120-
121-
GADT typechecking is an advanced feature that got significantly improved in this
122-
release. GADTs are case class hierarchies similar to this one:
119+
### Better and safer types in pattern matching (improved GADT support)
123120

121+
Consider the following implementation of an evaluator for a very simple
122+
language containing only integer literals (`Lit`) and pairs (`Pair`):
124123
```scala
125-
sealed trait Exp[T]
126-
case class IntLit(n: Int) extends Exp[Int]
127-
128-
case class GenLit[T](t: T) extends Exp[T]
129-
case class Fun[S, T](f: Exp[S] => Exp[T]) extends Exp[S => T]
130-
case class App[T, U](f: Exp[T => U], e: Exp[T]) extends Exp[U]
124+
sealed trait Exp
125+
case class Lit(value: Int) extends Exp
126+
case class Pair(fst: Exp, snd: Exp) extends Exp
127+
128+
object Evaluator {
129+
def eval(e: Exp): Any = e match {
130+
case Lit(x) =>
131+
x
132+
case Pair(a, b) =>
133+
(eval(a), eval(b))
134+
}
135+
def main(args: Array[String]): Unit = {
136+
println(eval(Lit(1))) // 1
137+
println(eval(Pair(Pair(Lit(1), Lit(2)), Lit(3)))) // ((1,2),3)
138+
}
139+
}
131140
```
132141

133-
where different constructors, such as `IntLit` and `Fun`, pass different type argument to the super
134-
trait. Hence, typechecking a pattern match on `v: Exp[T]` requires special care. For instance, if
135-
`v = IntLit(5)` then the typechecker must realize that `T` must be `Int`. This enables writing a
136-
typed interpreter `eval[T](e: Exp[T]): T`, where say the `IntLit` branch can return an `Int`:
142+
This code is correct but it's not very type-safe since `eval` returns a value
143+
of type `Any`, we can do better by adding a type parameter to `Exp` that
144+
represents the result type of evaluating expression:
137145

138146
```scala
139-
object Interpreter {
140-
def eval[T](e: Exp[T]): T = e match {
141-
case IntLit(n) => // Here T = Int and n: Int
142-
n
143-
144-
case gl: GenLit[_] => // Here in fact gl: GenLit[T]
145-
// the next line was incorrectly allowed before the fix to https://github.com/lampepfl/dotty/issues/1754
146-
// val gl1: GenLit[Nothing] = gl
147-
148-
gl.t
149-
150-
// The next cases triggered spurious warnings before the fix to
151-
// https://github.com/lampepfl/dotty/issues/3666
152-
153-
case f: Fun[s, t] => // Here T = s => t
154-
(v: s) => eval(f.f(GenLit(v)))
147+
sealed trait Exp[T]
148+
case class Lit(value: Int) extends Exp[Int]
149+
case class Pair[A, B](fst: Exp[A], snd: Exp[B]) extends Exp[(A, B)]
155150

156-
case App(f, e) => // Here f: Exp[s, T] and e: Exp[s]
157-
eval(f)(eval(e))
158-
}
151+
object Evaluator {
152+
def eval[T](e: Exp[T]): T = e match {
153+
case Lit(x) =>
154+
// In this case, T = Int
155+
x
156+
case Pair(a, b) =>
157+
// In this case, T = (A, B) where A is the type of a and B is the type of b
158+
(eval(a), eval(b))
159159
}
160160
```
161161

162-
Scala 2 and Dotty have issues typechecking such interpreters.
163-
In this release we fixed multiple bugs about GADT type checking and exhaustiveness checking, including
164-
[#3666](https://github.com/lampepfl/dotty/issues/3666),
165-
[#1754](https://github.com/lampepfl/dotty/issues/1754),
166-
[#3645](https://github.com/lampepfl/dotty/issues/3645),
167-
[#4030](https://github.com/lampepfl/dotty/issues/4030).
168-
Error messages are now more informative [#3990](https://github.com/lampepfl/dotty/pull/3990).
169-
170-
Fixes to covariant GADTs ([#3989](https://github.com/lampepfl/dotty/issues/3989)/
171-
[#4013](https://github.com/lampepfl/dotty/pull/4013)) have been deferred to next release.
162+
Now the expression `Pair(Pair(Lit(1), Lit(2)), Lit(3)))` has type `Exp[((Int,
163+
Int), Int)]` and calling `eval` on it will return a value of type `((Int,
164+
Int), Int)` instead of `Any`.
165+
166+
Something subtle is going on in the definition of `eval` here: its result type
167+
is `T` which is a type parameter that could be instantiated to anything, and
168+
yet in the `Lit` case we are able to return a value of type `Int`, and in the
169+
`Pair` case a value of a tuple type. In each case the typechecker has been able
170+
to constrain the type of `T` through unification (e.g. if `e` matches `Lit(x)`
171+
then `Lit` is a subtype of `Exp[T]`, so `T` must be equal to `Int`). This is
172+
usually referred to as **GADT support** in Scala since it closely mirrors the
173+
behavior of [Generalized Algebraic Data
174+
Types](https://en.wikipedia.org/wiki/Generalized_algebraic_data_type) in
175+
Haskell and other languages.
176+
177+
GADTs have been a part of Scala for a long time, but in Dotty 0.7.0-RC1 we
178+
significantly improved their implementation to catch more issues at
179+
compile-time. For example, writing `(eval(a), eval(a))` instead of `(eval(a),
180+
(eval(b)))` in the xample above should be an error, but it was not caught by
181+
Scala 2 or previous versions of Dotty, whereas we now get a type mismatch error
182+
as expected. More work remains to be done to fix the remaining [GADT-related
183+
issues](https://github.com/lampepfl/dotty/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+gadt),
184+
but so far no show-stopper has been found.
172185

173186
## Trying out Dotty
174187
### Scastie

0 commit comments

Comments
 (0)