Name that type! (nested types edition)
If we want to write a type for binary trees in Haskell, it is easy to do so:
> data BinaryTree a = Leaf > | InternalNode (BinaryTree a) a (BinaryTree a)The structure of the type guarantees certain things about its inhabitants. For instance, every node has exactly two children. If we want to guarantee stronger invariants, we can change the shape of the type. We can write a a type for complete binary tree (that is, binary trees in which every leaf is at the same depth), as
> data CompleteBinaryTree a = Leaves > | NonLeaves a (CompleteBinaryTree (a,a))The argument in the recursive part of this datatype is not the same as it is in the declaration. Datatypes with this property are called nested or non-regular or heterogeneous or non-uniform. We can express all sorts of invariants with these datatypes. Here's another example:
> data AList a b = Nil > | Cons a (AList b a)If we want to manipulate lists that alternate between two types
a and b, we could use the
type [Either a b], but this doesn't ensure that we don't
have two as or two bs in a row. We could,
instead, use the type [(a,b)], but this would only allow
us to create lists of even length. By switching the position of the
parameters in the recursion in AList, we can represent
alternating lists of any length.
Here are some other examples of data structure invariants we can ensure with nested types:
- Cyclic data structures (mirror)
- Many different types of lists with
fast
cons,snoc, concatenation, and indexing, including - Red-black trees (a paper, code for that paper), AVL trees (mirror, mirror 2)
- Square matrices, triangular matrices (in various works by Ralph Matthes), Toeplitz matrices, rectangular matrices (see "Manufacturing Datatypes", above)
- Efficient-merge heaps (see Okasaki's thesis & book, above), Braun trees, left-complete trees (see "Manufacturing Datatypes", above)
- Binomial queues (mirror, mirror 2)
- Tries (mirror, mirror 2, code, code mirror, old version, old version mirror) (See also Okasaki's thesis & book)
- Well-formed terms in the locally-nameless lambda calculus (mirror)
> data A x y = B (x y) > | C (A (D x) (x y)) > newtype D x y = D (y,x y) > newtype K x = K x > type QuestionOne = A K > > data E x = F x x > | G x (E x) > data QuestionTwo x = Q2 (E (E x)) > > data H x y = I > | J y x x (H (y,x) y) > type L = H () > data QuestionThreePartA x = Q3a (L x) (L x) (L x) > data QuestionThreePartB x = Q3b (L x) (QuestionThreePartA x)