As usual, this is a Literate Haskell file, with the obligatory header:
> {-# LANGUAGE StandaloneDeriving, TypeInType, TypeOperators, GADTs,
> TypeFamilies, UndecidableInstances #-}
> {-# OPTIONS_GHC -Wincomplete-patterns #-}
> module VecList where
> import Data.Kind ( Type )
> import Prelude hiding ( concat, (++) )
> data Nat where
> Zero :: Nat
> Succ :: Nat -> Nat
> type family (a :: Nat) + (b :: Nat) :: Nat where
> Zero + b = b
> Succ a + b = Succ (a + b)
> infixl 6 +
> type family (a :: Nat) * (b :: Nat) :: Nat where
> Zero * b = Zero
> Succ a * b = b + (a * b)
> infixl 7 *
> data Vec :: Nat -> Type -> Type where
> Nil :: Vec Zero a
> (:>) :: a -> Vec n a -> Vec (Succ n) a
> infixr 5 :>
> deriving instance Show a => Show (Vec n a)
> (++) :: Vec n a -> Vec m a -> Vec (n + m) a
> Nil ++ ys = ys
> (x :> xs) ++ ys = x :> (xs ++ ys)
> infixr 5 ++
Consider this standard-library function:
concat :: [[a]] -> [a]
concat [] = []
concat (xs : xss) = xs ++ concat xss
This function takes a list of lists and flattens it to just one list. So concat [[1,2], [3], [4, 5, 6]]
is [1,2,3,4,5,6]
.
Here’s how we might try to write it over Vec
s:
> concatRect :: Vec m (Vec n a) -> Vec (m * n) a
> concatRect Nil = Nil
> concatRect (xs :> xss) = xs ++ concatRect xss
Note that we’re using type-level multiplication now, with *
. This type family is defined above, in the introduction. (Its definition requires UndecidableInstances
. That flag disables GHC’s very simplistic termination checker for type families. Enabling the extension means that GHC might not terminate when trying to compile your file. But that’s a risk we’ll have to take.)
The problem with concatRect
is that it’s limiting. It allows us only to concatenate a rectangular arrangement of elements, where each Vec
stored within the larger Vec
has the same length n
. This function is thus inapplicable to our initial example with the numbers 1 through 6.
In order to contemplate a more general concat
, we need a new structure, for holding lists of Vec
s of uneven lengths. But we still want the structure’s type to record their lengths, so that we can sum all the lengths in the type of concat
. To do this, we’ll need type-level lists:
> data VecList :: [Nat] -> Type -> Type where
> VLNil :: VecList '[] a
> (:>>) :: Vec n a -> VecList ns a -> VecList (n ': ns) a
> infixr 5 :>>
The VecList
type is indexed by a type-level list of type-level Nat
s. Each element in the list is the index for one of the Vec
s stored in the list. (All elements have the same type a
.) In the VLNil
empty list case, we see that the type-level list of lengths is empty. (GHC requires that the promoted nil constructor and the promoted cons constructor are always written with their preceding ticks, as we see above.) In the :>>
case, we get a Vec n a
, a VecList ns a
(that is, a VecList
indexed by the type-level list of type-level Nat
s, which we’re calling ns
), producing a VecList (n ': ns) a
. That is, the result type just conses the new n
onto the list ns
.
We can now write the following translation of our original example:
> stuffs = (1 :> 2 :> Nil) :>> (3 :> Nil) :>> (4 :> 5 :> 6 :> Nil) :>> VLNil
GHCi can tell us
λ> :t stuffs
stuffs
:: VecList
'['Succ ('Succ 'Zero), 'Succ 'Zero, 'Succ ('Succ ('Succ 'Zero))]
Integer
That should be no surprise. The index to the type of stuffs
is '[2,1,3]
, corresponding to the lengths of the constituent elements of stuffs
.
Writing concat
is now relatively easy:
> type family Sum (ns :: [Nat]) :: Nat where
> Sum '[] = Zero
> Sum (n ': ns) = n + Sum ns
> concat :: VecList ns a -> Vec (Sum ns) a
> concat VLNil = Nil
> concat (xs :>> xss) = xs ++ concat xss
Note that, in the type of concat
, we must use a new type family Sum
that sums up all the elements in ns
. It would be ill-kinded to say Vec ns a
, because Vec
is indexed by a Nat
, but ns
is a [Nat]
.