Generalised algebraic datatype
- A short descriptions on generalised algebraic datatypes here as GHC language features
- Another description with links on Haskell' wiki
Generalised Algebraic Datatypes (GADTs) are datatypes for which a constructor has a non standard type. Indeed, in type systems incorporating GADTs, there are very few restrictions on the type that the data constructors can take. To show you how this could be useful, we will implement an evaluator for the typed SK calculus. Note that the K combinator is operationally similar to and, similarly, S is similar to the combinator which, in simply typed lambda calculus, have types and Without GADTs we would have to write something like this:
data Term = K | S | :@ Term Term infixl 6 :@
With GADTs, however, we can have the terms carry around more type information and create more interesting terms, like so:
data Term x where K :: Term (a -> b -> a) S :: Term ((a -> b -> c) -> (a -> b) -> a -> c) Const :: a -> Term a (:@) :: Term (a -> b) -> (Term a) -> Term b infixl 6 :@
now we can write a small step evaluator:
eval::Term a -> Term a eval (K :@ x :@ y) = x eval (S :@ x :@ y :@ z) = x :@ z :@ (y :@ z) eval x = x
Since the types of the so-called object language, being the typed SK calculus, are mimicked by the type system in our meta language, being haskell, we have a pretty convincing argument that the evaluator won't mangle our types. We say that typing is preserved under evaluation (preservation.) Note that this is an argument and not a proof.
This, however, comes at a price: let's see what happens when you try to convert strings into our object language:
parse "K" = K parse "S" = S
you'll get a nasty error like so:
Occurs check: cannot construct the infinite type: c = b -> c Expected type: Term ((a -> b -> c) -> (a -> b) -> a -> b -> c) Inferred type: Term ((a -> b -> c) -> (a -> b) -> a -> c) In the definition of `foo': foo "S" = S
One could, however, reason that parse has type: String -> exists a. Term a
Example with lists
here's another, smaller example:
data Empty data NonEmpty data List x y where Nil :: List a Empty Cons:: a -> List a b -> List a NonEmpty safeHead:: List x NonEmpty -> x safeHead (Cons a b) = a
now myhead can only be applied to non empty lists. This too comes at a cost; consider the function:
silly 0 = Nil silly 1 = Cons 1 Nil
yields an objection from ghc:
Couldn't match `Empty' against `NonEmpty' Expected type: List a Empty Inferred type: List a NonEmpty In the application `Cons 1 Nil' In the definition of `silly': silly 1 = Cons 1 Nil
An example: it seems to me that generalised algebraic datatypes can provide a nice solution to a problem described in the documentation of HaskellDB project: in Daan Leijen and Erik Meijer's paper (see PostScript version) on the original HaskellDB page: making typeful (safe) representation of terms of another language (here: SQL). In this example, the problem has been solved in a funny way with Phantom type
- we make first an untyped language,
- and then a typed one on top of it.
So we we destroy and rebuild -- is it a nice topic for a myth or scifi where a dreamworld is simulated on top of a previously homogenized world to look like the original?
But solving the problem with GADTs seems to be a more direct way (maybe that's why Simple unification-based type inference for GADTs mentions that they are also called as first-class phantom types?)
There are other developed tricks with types in Type, and another way to a more general framework in Dependent types. Epigram is a fully dependently typed language, and its Epigram tutorial (section 6.1) mentions that Haskell is closely related to Epigram, and attributes this relatedness e.g. exactly to the presence of GADTs.
The more general problem (representing the terms of a language with the terms of another language) can develop surprising things, e.g. quines (self-replicating or self-representing programs). More details and links on quines can be seen in the section Self-replication, quines, reflective programming of the page Combinatory logic.</haskell>