28 ms·
Generalized Algebraic Data Types, GADTs, are algebraic data types which include both (a) existential types and (b) type equalities. The combination allows for a
by tel 12y ago
Generalized Algebraic Data Types, GADTs, are algebraic data types which include both (a) existential types and (b) type equalities. The combination allows for a certain kind of interaction between the specific kind of value within a type and the type itself.
For instance, we might have a little expression language using normal ADTs [0]
type exp =
| Int of int -- integer constants
| Bool of bool -- bool constants
| Add of exp * exp -- addition
| Neg of exp -- negation
| Ifte of exp * exp * exp -- if then else
| Lte of exp * exp -- less than or equal to
but there's no particular guarantee that a value of `exp` is well-typed. For instance, the following is a valid value of `exp`
let x : exp = Add (Int 3, Bool true)
GADTs allow us to fix this situation by reflecting the type of the expression value into the type of the expression. Here's some syntax
type _ exp =
| Int : int -> int exp
| Bool : bool -> bool exp
| Add : int exp * int exp -> int exp
| Neg : int exp * int exp
| Ifte : bool exp * 'a exp * 'a exp -> 'a exp
| Lte : int exp * int exp -> bool exp
The way to read this syntax is to see it as giving the "type" of each constructor. Not only do we now parameterize the `exp` type with an index type corresponding to the type of the value this expression denotes, we also create very non-trivial type equations like
Ifte : bool exp -> 'a exp -> 'a exp -> 'a exp
and
Lte : int exp -> int exp -> bool exp
The upshot is that while the type of an interpreter for the first exp would have to look like
type value = VInt of int | VBool of bool
val interpret : exp -> value option
the type of the second `exp` is so constrained as to eliminate (a) the need for the separate `value` type and (b) the possibility of failure:
val interpret : 'a exp -> 'a
let interpret = function
| Int i -> i
| Bool b -> b
| Add (i1, i2) -> interpret i1 + interpret i2
| Neg i -> - (interpret i)
| Ifte (c, t, e) -> if interpret c then interpret t else interpret e
| Lte (i1, i2) -> interpret i1 <= interpret i2
Such a construction is known to be type-error free because GADTs allow us to ensure that type constructors follow certain type equations and it's possible to write such a clean evaluator because GADTs keep around enough type information so that the compiler can "trust" them.
Finally, the short answer of how they work is something like they interpret a constructor like
Ifte : bool exp * 'a exp * 'a exp -> 'a exp
as one with some existential types
Ifte : ... | 'x exp * 'a exp * 'a exp 'a ex
and some type equalities
Ifte : 'x == int | ...
then when typechecking it just has to collect all the unique new type variables and solve the system of equalities which arises.
[0] Yeah, the author pooh poohs at examples like this, but I think they're really particularly clear.
- gsg 12y agoI've come to think that these toy interpreter examples are a bit questionable unless they contain a variadic structure like an arbitrary tuple - they give the impression that programming with GADTs is simpler than it really is.
- taeric 12y agoWorse, to me, is that they don't seem to provide any evidence that the pattern can actually scale.
- tel 12y agoHow about this one for variadic structures? [0] data Rec (f :: k -> *) (ts :: [k]) where RNil :: Rec f '[] (:&) :: f t -> Rec f ts -> Rec f (t ': ts) infixr 9 :& data ElField f where Field :: KnownSymbol s => t -> ElField '(s, t) x :: Rec ElField ['("foo", Int), '("bar", String)] x = Field 3 :& Field "bar" :& RNil It's an embedding of (variadic) typed records. Anyway, I don't actually use GADT-driven embedded ASTs myself. They're just a pretty clear example. [0] This is Jon Sterling's `vinyl` package in Haskell
- gsg 12y agoYeah, that gives something of the flavour. (I was thinking of the record encoding at http://www.lexifi.com/blog/dynamic-types http://www.lexifi.com/blog/dynamic-types, myself.)
- joeyh 12y agoProblem with toy AST examples is they're great if you're doing something like that, but don't give much motivation if you're not. (Unless you starting seeing all programming as AST manipulation, which can be an interesting viewpoint.) Here's an example of another way to use GADTs. http://joeyh.name/blog/entry/making_propellor_safer_with_GADTs_and_type_families/ http://joeyh.name/blog/entry/making_propellor_safer_with_GAD... More important that using GADTs for one thing or another is to start looking at the type checker as something you use to assist you in writing the kind of code you want to write, and avoiding the kind of code you want to avoid. GADTs are one amoung many useful tools in the modern toolbox for that.