A common simplification when discussing many divide and conquer algorithms is the assumption that the input list has a size which is a power of two. As such, one might wonder: how do we encode lists that have power of two sizes, in a way that lists that don’t have this property are unrepresentable? One observation is that such lists are perfect binary trees, so if we have an encoding for perfect binary trees, we also have an encoding for power of two lists. Here are two well-known ways to do such an encoding in Haskell: one using GADTs and one using nested data-types. We claim that the nested data-types solution is superior.

This post is literate, but you will need some type system features:

{-# LANGUAGE ScopedTypeVariables, GADTs, ImpredicativeTypes #-}

GADTs One approach is to encode the size of the tree into the type, and then assert that the sizes of two trees are the same. This is pretty easy to do with GADTs: data Z data S n data L i a where L :: a -> L Z a N :: L i a -> L i a -> L (S i) a By reusing the type variable i , the constructor of N ensures that we any two trees we combine must have the same size. These trees can be destructed like normal binary trees: exampleL = N (N (L 1) (L 2)) (N (L 3) (L 4)) toListL :: L i a -> [a] -- type signature is necessary! toListL (L x) = [x] toListL (N l r) = toListL l ++ toListL r Creating these trees from ordinary lists is a little delicate, since the i type variable needs to be handled with care. Existentials over lists work fairly well: data L' a = forall i. L' { unL' :: L i a } data Ex a = forall i. Ex [L i a] fromListL :: [a] -> L' a fromListL xs = g (Ex (map L xs)) where g (Ex [x]) = L' x g (Ex xs) = g (Ex (f xs)) f (x:y:xs) = (N x y) : f xs f _ = []

Nested data-types Another approach is to literally build up a type isomorphic to a 2^n size tuple (modulo laziness). For example, in the case of a 4-tuple, we’d like to just say ((1, 2), (3, 4)) . There is still, however, the pesky question of how one does recursion over such a structure. The technique to use here is bootstrapping, described in Adam Buchsbaum in his thesis and popularized by Chris Okasaki: data B a = Two (B (a, a)) | One a deriving Show Notice how the recursive mention of B does not hold a , but (a, a) : this is so-called “non-uniform” recursion. Every time we apply a Two constructor, the size of our tuple doubles, until we top it off: exampleB = Two (Two (One ((1,2), (3,4)))) fromListB :: [a] -> B a fromListB [x] = One x fromListB xs = Two (fromListB (pairs xs)) where pairs (x:y:xs) = (x,y) : pairs xs pairs _ = [] toListB :: B a -> [a] toListB (One x) = [x] toListB (Two c) = concatMap (\(x,y) -> [x,y]) (toListB c)