Simple dependent type example in Haskell for Dummies. How are they useful in practice in Haskell? Why should I care about dependent types ? - haskell

I hear a lot about dependent types nowadays and I heard that DataKinds is somehow related to dependent typing (but I am not sure about this... just heard it on a Haskell Meetup).
Could someone illustrate with a super simple Haskell example what dependent typing is and what is it good for ?
On wikipedia it is written that dependent types can help prevent bugs. Could you give a simple example about how dependent types in Haskell can prevent bugs?
Something that I could start using in five minutes right now to prevent bugs in my Haskell code?
Dependent types are basically functions from values to types, how can this be used in practice? Why is that good ?

Late to the party, this answer is basically a shameless plug.
Sam Lindley and I wrote a paper about Hasochism, the pleasure and pain of dependently typed programming in Haskell. It gives plenty of examples of what's possible now in Haskell and draws points of comparison (favourable as well as not) with the Agda/Idris generation of dependently typed languages.
Although it is an academic paper, it is about actual programs, and you can grab the code from Sam's repo. We have lots of little examples (e.g. orderedness of mergesort output) but we end up with a text editor example, where we use indexing by width and height to manage screen geometry: we make sure that components are regular rectangles (vectors of vectors, not ragged lists of lists) and that they fit together exactly.
The key power of dependent types is to maintain consistency between separate data components (e.g., the head vector in a matrix and every vector in its tail must all have the same length). That's never more important than when writing conditional code. The situation (which will one day come to be seen as having been ridiculously naïve) is that the following are all type-preserving rewrites
if b then t else e => if b then e else t
if b then t else e => t
if b then t else e => e
Although we are presumably testing b because it gives us some useful insight into what would be appropriate (or even safe) to do next, none of that insight is mediated via the type system: the idea that b's truth justifies t and its falsity justifies e is missing, despite being critical.
Plain old Hindley-Milner does give us one means to ensure some consistency. Whenever we have a polymorphic function
f :: forall a. r[a] -> s[a] -> t[a]
we must instantiate a consistently: however the first argument fixes a, the second argument must play along, and we learn something useful about the result while we are at it. Allowing data at the type level is useful because some forms of consistency (e.g. lengths of things) are more readily expressed in terms of data (numbers).
But the real breakthrough is GADT pattern matching, where the type of a pattern can refine the type of the argument it matches. You have a vector of length n; you look to see whether it's nil or cons; now you know whether n is zero or not. This is a form of testing where the type of the code in each case is more specific than the type of the whole, because in each case something which has been learned is reflected at the type level. It is learning by testing which makes a language dependently typed, at least to some extent.
Here's a silly game to play, whatever typed language you use. Replace every type variable and every primitive type in your type expressions with 1 and evaluate types numerically (sum the sums, multiply the products, s -> t means t-to-the-s) and see what you get: if you get 0, you're a logician; if you get 1, you're a software engineer; if you get a power of 2, you're an electronic engineer; if you get infinity, you're a programmer. What's going on in this game is a crude attempt to measure the information we're managing and the choices our code must make. Our usual type systems are good at managing the "software engineering" aspects of coding: unpacking and plugging together components. But as soon as a choice has been made, there is no way for types to observe it, and as soon as there are choices to make, there is no way for types to guide us: non-dependent type systems approximate all values in a given type as the same. That's a pretty serious limitation on their use in bug prevention.

The common example is to encode the length of a list in it's type, so you can do things like (pseudo code).
cons :: a -> List a n -> List a (n+1)
Where n is an integer. This let you specify that adding an object to list increment its length by one.
You can then prevent head (which give you the first element of a list) to be ran on empty list
head :: n > 0 => List a n -> a
Or do things like
to3uple :: List a 3 -> (a,a,a)
The problem with this type of approach is you then can't call head on a arbitrary list without having proven first that the list is not null.
Sometime the proof can be done by the compiler, ex:
head (a `cons` l)
Otherwise, you have to do things like
if null list
then ...
else (head list)
Here it's safe to call head, because you are in the else branch and therefore guaranteed that the length is not null.
However, Haskell doesn't do dependent type at the moment, all the examples have given won't work as nicely, but you should be able to declare this type of list using DataKind because you can promote a int to type which allow to instanciate List a b with List Int 1. (b is a phantom type taking a literal).
If you are interested in this type of safety, you can have a look a liquid Haskell.
Here is a example of such code
{-# LANGUAGE DataKinds, KindSignatures, TypeFamilies, TypeOperators #-}
import GHC.TypeLits
data List a (n:: Nat) = List [a] deriving Show
cons :: a -> List a n -> List a (n + 1)
cons x (List xs) = List (x:xs)
singleton :: a -> List a 1
singleton x = List [x]
data NonEmpty
data EmptyList
type family ListLength a where
ListLength (List a 0) = EmptyList
ListLength (List a n) = NonEmpty
head' :: (ListLength (List a n) ~ NonEmpty) => List a n -> a
head' (List xs) = head xs
tail' :: (ListLength (List a n) ~ NonEmpty) => List a n -> List a (n-1)
tail' (List xs) = List (tail xs)
list = singleton "a"
head' list -- return "a"
Trying to do head' (tail' list) doesn't compile and give
Couldn't match type ‘EmptyList’ with ‘NonEmpty’
Expected type: NonEmpty
Actual type: ListLength (List [Char] 0)
In the expression: head' (tail' list)
In an equation for ‘it’: it = head' (tail' list)

Adding to #mb14's example, here's some simpler working code.
First, we need DataKinds, GADTs, and KindSignatures to really make it clear:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTS #-}
{-# LANGUAGE KindSignatures #-}
Now let's define a Nat type, and a Vector type based on it:
data Nat :: * where
Z :: Nat
S :: Nat -> Nat
data Vector :: Nat -> * -> * where
Nil :: Vector Z a
(:-:) :: a -> Vector n a -> Vector (S n) a
And voila, lists using dependent types that can be called safe in certain circumstances.
Here are the head and tail functions:
head' :: Vector (S n) a -> a
head' (a :-: _) = a
-- The other constructor, Nil, doesn't apply here because of the type signature!
tail' :: Vector (S n) a -> Vector n a
tail (_ :-: xs) = xs
-- Ditto here.
This is a more concrete and understandable example than above, but does the same sort of thing.
Note that in Haskell, Types can influence values, but values cannot influence types in the same dependent ways. There are languages such as Idris that are similar to Haskell but also support value-to-type dependent typing, which I would recommend looking into.

The machines package lets users define machines that can request values. Many machines request only one type of value, but it's also possible to define machines that sometimes ask for one type and sometimes ask for another type. The requests are values of a GADT type, which allows the value of the request to determine the type of the response.
Step k o r = ...
| forall t . Await (t -> r) (k t) r
The machine provides a request of type k t for some unspecified type t, and a function to deal with the result. By pattern matching on the request, the machine runner learns what type it must supply the machine. The machine's response handler doesn't need to check that it got the right sort of response.

Related

How do we overcome the compile time and runtime gap when programming in a Dependently Typed Language?

I'm told that in dependent type system, "types" and "values" is mixed, and we can treat both of them as "terms" instead.
But there is something I can't understand: in a strongly typed programming language without Dependent Type (like Haskell), Types is decided (infered or checked) at compile time, but values is decided (computed or inputed) at runtime.
I think there must be a gap between these two stages. Just think that if a value is interactively read from STDIN, how can we reference this value in a type which must be decided AOT?
e.g. There is a natural number n and a list of natural number xs (which contains n elements) which I need to read from STDIN, how can I put them into a data structure Vect n Nat?
Suppose we input n :: Int at runtime from STDIN. We then read n strings, and store them into vn :: Vect n String (pretend for the moment this can be done).
Similarly, we can read m :: Int and vm :: Vect m String. Finally, we concatenate the two vectors: vn ++ vm (simplifying a bit here). This can be type checked, and will have type Vect (n+m) String.
Now it is true that the type checker runs at compile time, before the values n,m are known, and also before vn,vm are known. But this does not matter: we can still reason symbolically on the unknowns n,m and argue that vn ++ vm has that type, involving n+m, even if we do not yet know what n+m actually is.
It is not that different from doing math, where we manipulate symbolic expressions involving unknown variables according to some rules, even if we do not know the values of the variables. We don't need to know what number is n to see that n+n = 2*n.
Similarly, the type checker can type check
-- pseudocode
readNStrings :: (n :: Int) -> IO (Vect n String)
readNStrings O = return Vect.empty
readNStrings (S p) = do
s <- getLine
vp <- readNStrings p
return (Vect.cons s vp)
(Well, actually some more help from the programmer could be needed to typecheck this, since it involves dependent matching and recursion. But I'll neglect this.)
Importantly, the type checker can check that without knowing what n is.
Note that the same issue actually already arises with polymorphic functions.
fst :: forall a b. (a, b) -> a
fst (x, y) = x
test1 = fst # Int # Float (2, 3.5)
test2 = fst # String # Bool ("hi!", True)
...
One might wonder "how can the typechecker check fst without knowing what types a and b will be at runtime?". Again, by reasoning symbolically.
With type arguments this is arguably more obvious since we usually run the programs after type erasure, unlike value parameters like our n :: Int above, which can not be erased. Still, there is some similarity between universally quantifying over types or over Int.
It seems to me that there are two questions here:
Given that some values are unknown during compile-time (e.g., values read from STDIN), how can we make use of them in types? (Note that chi has already given an excellent answer to this.)
Some operations (e.g., getLine) seem to make absolutely no sense at compile-time; how could we possibly talk about them in types?
The answer to (1), as chi has said, is symbolic or abstract reasoning. You can read in a number n, and then have a procedure that builds a Vect n Nat by reading from the command line n times, making use of arithmetic properties such as the fact that 1+(n-1) = n for nonzero natural numbers.
The answer to (2) is a bit more subtle. Naively, you might want to say "this function returns a vector of length n, where n is read from the command line". There are two types you might try to give this (apologies if I'm getting Haskell notation wrong)
unsafePerformIO (do n <- getLine; return (IO (Vect (read n :: Int) Nat)))
or (in pseudo-Coq notation, since I'm not sure what Haskell's notation for existential types is)
IO (exists n, Vect n Nat)
These two types can actually both be made sense of, and say different things. The first type, to me, says "at compile time, read n from the command line, and return a function which, at runtime, gives a vector of length n by performing IO". The second type says "at runtime, perform IO to get a natural number n and a vector of length n".
The way I like looking at this is that all side effects (other than, perhaps, non-termination) are monad transformers, and there is only one monad: the "real-world" monad. Monad transformers work just as well at the type level as at the term level; the one thing which is special is run :: M a -> a which executes the monad (or stack of monad transformers) in the "real world". There are two points in time at which you can invoke run: one is at compile time, where you invoke any instance of run which shows up at the type level. Another is at runtime, where you invoke any instance of run which shows up at the value level. Note that run only makes sense if you specify an evaluation order; if your language does not specify whether it is call-by-value or call-by-name (or call-by-push-value or call-by-need or call-by-something-else), you can get incoherencies when you try to compute a type.

How can quotient types help safely expose module internals?

Reading up on quotient types and their use in functional programming, I came across this post. The author mentions Data.Set as an example of a module which provides a ton of functions which need access to module's internals:
Data.Set has 36 functions, when all that are really needed to ensure the meaning of a set ("These elements are distinct") are toList and fromList.
The author's point seems to be that we need to "open up the module and break the abstraction" if we forgot some function which can be implemented efficiently only using module's internals.
He then says
We could alleviate all of this mess with quotient types.
but gives no explanation to that claim.
So my question is: how are quotient types helping here?
EDIT
I've done a bit more research and found a paper "Constructing Polymorphic Programs with Quotient Types". It elaborates on declaring quotient containers and mentions the word "efficient" in abstract and introduction. But if I haven't misread, it does not give any example of an efficient representation "hiding behind" a quotient container.
EDIT 2
A bit more is revealed in "[PDF] Programming in Homotopy Type Theory" paper in Chapter 3. The fact that quotient type can be implemented as a dependent sum is used. Views on abstract types are introduced (which look very similar to type classes to me) and some relevant Agda code is provided. Yet the chapter focuses on reasoning about abstract types, so I'm not sure how this relates to my question.
I recently made a blog post about quotient types, and I was led here by a comment. The blog post may provide some additional context in addition to the papers referenced in the question.
The answer is actually pretty straightforward. One way to arrive at it is to ask the question: why are we using an abstract data type in the first place for Data.Set?
There are two distinct and separable reasons. The first reason is to hide the internal type behind an interface so that we can substitute a completely new type in the future. The second reason is to enforce implicit invariants on values of the internal type. Quotient type and their dual subset types allow us to make the invariants explicit and enforced by the type checker so that we no longer need to hide the representation. So let me be very clear: quotient (and subset) types do not provide you with any implementation hiding. If you implement Data.Set with quotient types using lists as your representation, then later decide you want to use trees, you will need to change all code that uses your type.
Let's start with a simpler example (leftaroundabout's). Haskell has an Integer type but not a Natural type. A simple way to specify Natural as a subset type using made up syntax would be:
type Natural = { n :: Integer | n >= 0 }
We could implement this as an abstract type using a smart constructor that threw an error when given a negative Integer. This type says that only a subset of the values of type Integer are valid. Another approach we could use to implement this type is to use a quotient type:
type Natural = Integer / ~ where n ~ m = abs n == abs m
Any function h :: X -> T for some type T induces a quotient type on X quotiented by the equivalence relation x ~ y = h x == h y. Quotient types of this form are more easily encoded as abstract data types. In general, though, there may not be such a convenient function, e.g.:
type Pair a = (a, a) / ~ where (a, b) ~ (x, y) = a == x && b == y || a == y && b == x
(As to how quotient types relate to setoids, a quotient type is a setoid that enforces that you respect its equivalence relation.) This second definition of Natural has the property that there are two values that represent 2, say. Namely, 2 and -2. The quotient type aspect says we are allowed to do whatever we want with the underlying Integer, so long as we never produce a result that differentiates between these two representatives. Another way to see this is that we can encode a quotient type using subset types as:
X/~ = forall a. { f :: X -> a | forEvery (\(x, y) -> x ~ y ==> f x == f y) } -> a
Unfortunately, that forEvery is tantamount to checking equality of functions.
Zooming back out, subset types add constraints on producers of values and quotient types add constraints on consumers of values. Invariants enforced by an abstract data type may be a mixture of these. Indeed, we may decide to represent a Set as the following:
data Tree a = Empty | Branch (Tree a) a (Tree a)
type BST a = { t :: Tree a | isSorted (toList t) }
type Set a = { t :: BST a | noDuplicates (toList t) } / ~
where s ~ t = toList s == toList t
Note, nothing about this ever requires us to actually execute isSorted, noDuplicates, or toList. We "merely" need to convince the type checker that the implementations of functions on this type would satisfy these predicates. The quotient type allows us to have a redundant representation while enforcing that we treat equivalent representations in the same way. This doesn't mean we can't leverage the specific representation we have to produce a value, it just means that we must convince the type checker that we would have produced the same value given a different, equivalent representation. For example:
maximum :: Set a -> a
maximum s = exposing s as t in go t
where go Empty = error "maximum of empty Set"
go (Branch _ x Empty) = x
go (Branch _ _ r) = go r
The proof obligation for this is that the right-most element of any binary search tree with the same elements is the same. Formally, it's go t == go t' whenever toList t == toList t'. If we used a representation that guaranteed the tree would be balanced, e.g. an AVL tree, this operation would be O(log N) while converting to a list and picking the maximum from the list would be O(N). Even with this representation, this code is strictly more efficient than converting to a list and getting the maximum from the list. Note, that we could not implement a function that displayed the tree structure of the Set. Such a function would be ill-typed.
I'll give a simpler example where it's reasonably clear. Admittedly I myself don't really see how this would translate to something like Set, efficiently.
data Nat = Nat (Integer / abs)
To use this safely, we must be sure that any function Nat -> T (with some non-quotient T, for simplicity's sake) does not depend on the actual integer value, but only on its absolute. To do so, it's not really necessary to hide Integer completely; it would be sufficient to prevent you from matching on it directly. Instead, the compiler might rewrite the matches, e.g.
even' :: Nat -> Bool
even' (Nat 0) = True
even' (Nat 1) = False
even' (Nat n) = even' . Nat $ n - 2
could be rewritten to
even' (Nat n') = case abs n' of
[|abs 0|] -> True
[|abs 1|] -> False
n -> even' . Nat $ n - 2
Such a rewriting would point out equivalence violations, e.g.
bad (Nat 1) = "foo"
bad (Nat (-1)) = "bar"
bad _ = undefined
would rewrite to
bad (Nat n') = case n' of
1 -> "foo"
1 -> "bar"
_ -> undefined
which is obviously an overlapped pattern.
Disclaimer: I just read up on quotient types upon reading this question.
I think the author's just saying that sets can be described as quotient types over lists. Ie: (making up some haskell-like syntax):
data Set a = Set [a] / (sort . nub) deriving (Eq)
Ie, a Set a is just a [a] with equality between two Set a's determined by whether the sort . nub of the underlying lists are equal.
We could do this explicitly like this, I guess:
import Data.List
data Set a = Set [a] deriving (Show)
instance (Ord a, Eq a) => Eq (Set a) where
(Set xs) == (Set ys) = (sort $ nub xs) == (sort $ nub ys)
Not sure if this is actually what the author intended as this isn't a particularly efficient way of implementing a set. Someone can feel free to correct me.

Why can't I compare tuples of arbitrary length in Haskell?

I know that there are predefined Eq instances for tuples of lengths 2 to 15.
Why aren't tuples defined as some kind of recursive datatype such that they can be decomposed, allowing a definition of a function for a compare that works with arbitrary length tuples?
After all, the compiler does support arbitrary length tuples.
You might ask yourself what the type of that generalized comparison function would be. First of all we need a way to encode the component types:
data Tuple ??? = Nil | Cons a (Tuple ???)
There is really nothing valid we can replace the question marks with. The conclusion is that a regular ADT is not sufficient, so we need our first language extension, GADTs:
data Tuple :: ??? -> * where
Nil :: Tuple ???
Cons :: a -> Tuple ??? -> Tuple ???
Yet we end up with question marks. Filling in the holes requires another two extensions, DataKinds and TypeOperators:
data Tuple :: [*] -> * where
Nil :: Tuple '[]
Cons :: a -> Tuple as -> Tuple (a ': as)
As you see we needed three type system extensions just to encode the type. Can we compare now? Well, it's not that straightforward to answer, because it's actually far from obvious how to write a standalone comparison function. Luckily the type class mechanism allows us to take a simple recursive approach. However, this time we are not just recursing on the value level, but also on the type level. Obviously empty tuples are always equal:
instance Eq (Tuple '[]) where
_ == _ = True
But the compiler complains again. Why? We need another extension, FlexibleInstances, because '[] is a concrete type. Now we can compare empty tuples, which isn't that compelling. What about non-empty tuples? We need to compare the heads as well as the rest of the tuple:
instance (Eq a, Eq (Tuple as)) => Eq (Tuple (a ': as)) where
Cons x xs == Cons y ys = x == y && xs == ys
Seems to make sense, but boom! We get another complaint. Now the compiler wants FlexibleContexts, because we have a not-fully-polymorphic type in the context, Tuple as.
That's a total of five type system extensions, three of them just to express the tuple type, and they didn't exist before GHC 7.4. The other two are needed for comparison. Of course there is a payoff. We get a very powerful tuple type, but because of all those extensions, we obviously can't put such a tuple type into the base library.
You can always rewrite any n-tuple in terms of binary tuples. For example, given the following 4-tuple:
(1, 'A', "Hello", 20)
You can rewrite it as:
(1, ('A', ("Hello", (20, ()))))
Think of it as a list, where (,) plays the role of (:) (i.e. "cons") and () plays the role of [] (i.e. "nil"). Using this trick, as long as you formulate your n-tuple in terms of a "list of binary tuples", then you can expand it indefinitely and it will automatically derive the correct Eq and Ord instances.
A type of compare is a -> a -> Ordering, which suggests that both of the inputs must be of the same type. Tuples of different arities are by definition different types.
You can however solve your problem by approaching it either with HLists or GADTs.
I just wanted to add to ertes' answer that you don't need a single extension to do this. The following code should be haskell98 as well as 2010 compliant. And the datatypes therein can be mapped one on one to tuples with the exception of the singleton tuple. If you do the recursion after the two-tuple you could also achieve that.
module Tuple (
TupleClass,
TupleCons(..),
TupleNull(..)
) where
class (TupleClassInternal t) => TupleClass t
class TupleClassInternal t
instance TupleClassInternal ()
instance TupleClassInternal (TupleCons a b)
data (TupleClassInternal b) => TupleCons a b = TupleCons a !b deriving (Show)
instance (Eq a, Eq b, TupleClass b) => Eq (TupleCons a b) where
(TupleCons a1 b1) == (TupleCons a2 b2) = a1 == a2 && b1 == b2
You could also just derive Eq. Of course it would look a bit cooler with TypeOperators but haskell's list system has syntactical sugar too.

What is the significance of algebraic datatypes with zero constructors?

This passage, which unfortunately lacks references, about the development of ADTs in Haskell, from A History of Haskell: Being Lazy With Class, section 5.1:
In general, an algebraic type specifies a sum of one or more
alternatives, where each alternative is a product of zero or more
fields. It might have been useful to permit a sum of zero
alternatives, which would be a completely empty type, but at the time
the value of such a type was not appreciated.
leaves me wondering, how would such an ADT be useful?
Theoretically: the Curry-Howard isomorphism gives us an interpretation of this type as the "false" proposition. "false" is useful as a proposition on its own; but is also useful for constructing the "not" combinator (as type Not a = a -> False) and other similar constructions.
Pragmatically: this type can be used to prevent certain branches of parameterized data types from coming into existence. For example, I've used this in a library for parsing various game trees something like this:
data RuleSet a = Known !a | Unknown String
data GoRuleChoices = Japanese | Chinese
data LinesOfActionChoices -- there are none in the spec!
type GoRuleSet = RuleSet GoRuleChoices
type LinesOfActionRuleSet = RuleSet LinesOfActionChoices
The impact of this is that, when parsing a Lines of Action game tree, if there's a ruleset specified, we know its constructor will be Unknown, and can leave other branches off during pattern matches, etc.
Among corresponding to logical false (as stated in another answer), they are often used to create additional type constraints in combination with GADTs. For example:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE EmptyDataDecls #-}
import Data.List (groupBy)
data Zero
data Succ n
data Vec n a where
Nil :: Vec Zero a
Cons :: a -> Vec n a -> Vec (Succ n) a
vhead :: Vec (Succ n) a -> a
vhead (Cons v _) = v
vtail :: Vec (Succ n) a -> Vec n a
vtail (Cons _ v) = v
Here we have two such data types with no constructor. Their meaning here is just to represent natural numbers: Zero, Succ Zero, Succ (Succ Zero) etc. They are used as phantom types in Vec data type so that we can encode the length of a vector in its type. Then, we can write type-safe functions such as vhead/vtail that can be applied only to non-empty vectors.
See also [Haskell] Fixed-length vectors in Haskell, Part 1: Using GADTs where the example is elaborated in detail.
There is no way to construct a "real" value of a no-constructor type (where by "real" I mean a terminating computation; Haskell has undefined :: a, error :: String -> a and the possibility of writing nonterminating programs like mwahaha = mwahaha, which to be simplistic I'll call "fake" values).
One example of how this can be useful is versions 0.5 and later of the conduit library. The basic type in the library is Pipe l i o u m r; with different parameters for these types, a Pipe can serve either as a source (which produces output without consuming any input), a sink (consumes input without producing any output), or a conduit (consumes input and produces output). The i and o type parameters to Pipe are the types of its input and output, respectively.
So one of the ways the conduit library enforces the notion that sources consume no input and sinks produce no output by using the Void type from Data.Void as the input type for sources and output type for sinks. Again, there is no terminating way to construct a value of such a type, so a program that attempts to consume output from a sink will not terminate (which as a reminder, in Haskell this can mean "raise an error" and not necessarily "loop forever").
Types with no constructors are called phantom types. See the page in the Haskell wiki.

Real world use of GADT

How do I make use of Generalized Algebraic Data Type?
The example given in the haskell wikibook is too short to give me an insight of the real possibilities of GADT.
GADTs are weak approximations of inductive families from dependently typed languages—so let's begin there instead.
Inductive families are the core datatype introduction method in a dependently typed language. For instance, in Agda you define the natural numbers like this
data Nat : Set where
zero : Nat
succ : Nat -> Nat
which isn't very fancy, it's essentially just the same thing as the Haskell definition
data Nat = Zero | Succ Nat
and indeed in GADT syntax the Haskell form is even more similar
{-# LANGUAGE GADTs #-}
data Nat where
Zero :: Nat
Succ :: Nat -> Nat
So, at first blush you might think GADTs are just neat extra syntax. That's just the very tip of the iceberg though.
Agda has capacity to represent all kinds of types unfamiliar and strange to a Haskell programmer. A simple one is the type of finite sets. This type is written like Fin 3 and represents the set of numbers {0, 1, 2}. Likewise, Fin 5 represents the set of numbers {0,1,2,3,4}.
This should be quite bizarre at this point. First, we're referring to a type which has a regular number as a "type" parameter. Second, it's not clear what it means for Fin n to represent the set {0,1...n}. In real Agda we'd do something more powerful, but it suffices to say that we can define a contains function
contains : Nat -> Fin n -> Bool
contains i f = ?
Now this is strange again because the "natural" definition of contains would be something like i < n, but n is a value that only exists in the type Fin n and we shouldn't be able to cross that divide so easily. While it turns out that the definition is not nearly so straightforward, this is exactly the power that inductive families have in dependently typed languages—they introduce values that depend on their types and types that depend on their values.
We can examine what it is about Fin that gives it that property by looking at its definition.
data Fin : Nat -> Set where
zerof : (n : Nat) -> Fin (succ n)
succf : (n : Nat) -> (i : Fin n) -> Fin (succ n)
this takes a little work to understand, so as an example lets try constructing a value of the type Fin 2. There are a few ways to do this (in fact, we'll find that there are exactly 2)
zerof 1 : Fin 2
zerof 2 : Fin 3 -- nope!
zerof 0 : Fin 1 -- nope!
succf 1 (zerof 0) : Fin 2
This lets us see that there are two inhabitants and also demonstrates a little bit of how type computation happens. In particular, the (n : Nat) bit in the type of zerof reflects the actual value n up into the type allowing us to form Fin (n+1) for any n : Nat. After that we use repeated applications of succf to increment our Fin values up into the correct type family index (natural number that indexes the Fin).
What provides these abilities? In all honesty there are many differences in between a dependently typed inductive family and a regular Haskell ADT, but we can focus on the exact one that is most relevant to understanding GADTs.
In GADTs and inductive families you get an opportunity to specify the exact type of your constructors. This might be boring
data Nat where
Zero :: Nat
Succ :: Nat -> Nat
Or, if we have a more flexible, indexed type we can choose different, more interesting return types
data Typed t where
TyInt :: Int -> Typed Int
TyChar :: Char -> Typed Char
TyUnit :: Typed ()
TyProd :: Typed a -> Typed b -> Typed (a, b)
...
In particular, we're abusing the ability to modify the return type based on the particular value constructor used. This allows us to reflect some value information up into the type and produce more finely specified (fibered) typed.
So what can we do with them? Well, with a little bit of elbow grease we can produce Fin in Haskell. Succinctly it requires that we define a notion of naturals in types
data Z
data S a = S a
> undefined :: S (S (S Z)) -- 3
... then a GADT to reflect values up into those types...
data Nat where
Zero :: Nat Z
Succ :: Nat n -> Nat (S n)
... then we can use these to build Fin much like we did in Agda...
data Fin n where
ZeroF :: Nat n -> Fin (S n)
SuccF :: Nat n -> Fin n -> Fin (S n)
And finally we can construct exactly two values of Fin (S (S Z))
*Fin> :t ZeroF (Succ Zero)
ZeroF (Succ Zero) :: Fin (S (S Z))
*Fin> :t SuccF (Succ Zero) (ZeroF Zero)
SuccF (Succ Zero) (ZeroF Zero) :: Fin (S (S Z))
But notice that we've lost a lot of convenience over the inductive families. For instance, we can't use regular numeric literals in our types (though that's technically just a trick in Agda anyway), we need to create a separate "type nat" and "value nat" and use the GADT to link them together, and we'd also find, in time, that while type level mathematics is painful in Agda it can be done. In Haskell it's incredibly painful and often cannot.
For instance, it's possible to define a weaken notion in Agda's Fin type
weaken : (n <= m) -> Fin n -> Fin m
weaken = ...
where we provide a very interesting first value, a proof that n <= m which allows us to embed "a value less than n" into the set of "values less than m". We can do the same in Haskell, technically, but it requires heavy abuse of type class prolog.
So, GADTs are a resemblance of inductive families in dependently typed languages that are weaker and clumsier. Why do we want them in Haskell in the first place?
Basically because not all type invariants require the full power of inductive families to express and GADTs pick a particular compromise between expressiveness, implementability in Haskell, and type inference.
Some examples of useful GADTs expressions are Red-Black Trees which cannot have the Red-Black property invalidated or simply-typed lambda calculus embedded as HOAS piggy-backing off the Haskell type system.
In practice, you also often see GADTs use for their implicit existential context. For instance, the type
data Foo where
Bar :: a -> Foo
implicitly hides the a type variable using existential quantification
> :t Bar 4 :: Foo
in a way that is sometimes convenient. If you look carefully the HOAS example from Wikipedia uses this for the a type parameter in the App constructor. To express that statement without GADTs would be a mess of existential contexts, but the GADT syntax makes it natural.
GADTs can give you stronger type enforced guarantees than regular ADTs. For example, you can force a binary tree to be balanced on the type system level, like in this implementation of 2-3 trees:
{-# LANGUAGE GADTs #-}
data Zero
data Succ s = Succ s
data Node s a where
Leaf2 :: a -> Node Zero a
Leaf3 :: a -> a -> Node Zero a
Node2 :: Node s a -> a -> Node s a -> Node (Succ s) a
Node3 :: Node s a -> a -> Node s a -> a -> Node s a -> Node (Succ s) a
Each node has a type-encoded depth where all its leaves reside. A tree is then
either an empty tree, a singleton value, or a node of unspecified depth, again
using GADTs.
data BTree a where
Root0 :: BTree a
Root1 :: a -> BTree a
RootN :: Node s a -> BTree a
The type system guarantees you that only balanced nodes can be constructed.
This means that when implementing operations like insert on such trees, your
code type-checks only if its result is always a balanced tree.
I have found the "Prompt" monad (from the "MonadPrompt" package) a very useful tool in several places (along with the equivalent "Program" monad from the "operational" package. Combined with GADTs (which is how it was intended to be used), it allows you to make embedded languages very cheaply and very flexibly. There was a pretty good article in the Monad Reader issue 15 called "Adventures in Three Monads" that had a good introduction to the Prompt monad along with some realistic GADTs.
I like the example in the GHC manual. It's a quick demo of a core GADT idea: that you can embed the type system of a language you're manipulating into Haskell's type system. This lets your Haskell functions assume, and forces them to preserve, that the syntax trees correspond to well-typed programs.
When we define Term, it doesn't matter what types we choose. We could write
data Term a where
...
IsZero :: Term Char -> Term Char
or
...
IsZero :: Term a -> Term b
and the definition of Term would still go through.
It's only once we want to compute on Term, such as in defining eval, that the types matter. We need to have
...
IsZero :: Term Int -> Term Bool
because we need our recursive call to eval to return an Int, and we want to in turn return a Bool.
This is a short answer, but consult the Haskell Wikibook. It walks you though a GADT for a well-typed expression tree, which is a fairly canonical example: http://en.wikibooks.org/wiki/Haskell/GADT
GADTs are also used for implementing type equality: http://hackage.haskell.org/package/type-equality. I can't find the right paper to reference for this offhand -- this technique has made its way well into folklore by now. It is used quite well, however, in Oleg's typed tagless stuff. See, e.g. the section on typed compilation into GADTs. http://okmij.org/ftp/tagless-final/#tc-GADT

Resources