TRANSPOSITION FOR FIXED-SIZED MATRICES Problem-Solving Club 2nd December 2005 We generalize the approach shown in "Transposition for Three-by-Three Matrices" to matrices of arbitrary size, but with that size enforced at the type level. This script needs GHC extensions - specifically, GADTs. (So it requires GHC 6.4 or later.) > {-# OPTIONS -fglasgow-exts #-} The types Z and S are uninhabited, but are used to index GADTs (by size). We call types of the form S(S(Z)) "type-level naturals". > data Z > data S n The type Vector is a polymorphic GADT, indexed by a type-level natural (the length of the vector) and an element type. Note that the type system enforces the property that all inhabitants of a type Vector n a have the same length. > data Vector n a where > VNil :: Vector Z a > VCons :: a -> Vector n a -> Vector (S n) a For simplicity, we allow ourselves to flatten a vector to a list. > toList :: Vector n a -> [a] > toList VNil = [] > toList (VCons a x) = a : toList x > instance Show a => Show (Vector n a) where > show = show . toList Two simple shapely "wholemeal" operations on vectors that we will use are map and fork on vectors. You can think of both of these as being instances of "zip with apply", where in each case one of the two lists is constant. > vmap :: (a->b) -> Vector n a -> Vector n b > vmap f VNil = VNil > vmap f (VCons a x) = VCons (f a) (vmap f x) > vfork :: Vector n (a->b) -> a -> Vector n b > vfork VNil a = VNil > vfork (VCons f fs) a = VCons (f a) (vfork fs a) Other operations on vectors are less wholesome; they work on single elements. Indexing is a typical example. For this to be safe, the index selected has to be less than the vector length (we index from zero); we specify this at the type level by a indexing parameter. We therefore need some machinery for specifying and manipulating those index parameters. The type Nat is a GADT, indexed by a type-level natural. For size n, the type Nat n has a single inhabitant: a term constructed from the constructors Z and S of the same structure as the type index itself. > data Nat n where > Z :: Nat Z > S :: Nat n -> Nat (S n) For example, here is a nine-by-nine matrix, with the numbers from 1..81. > type Nine = S(S(S(S(S(S(S(S(S Z)))))))) > nine :: Nat Nine > nine = S(S(S(S(S(S(S(S(S Z)))))))) > viter :: Nat n -> (a->a) -> a -> Vector n a > viter Z f a = VNil > viter (S n) f a = VCons a (viter n f (f a)) > mat :: Vector Nine (Vector Nine Int) > mat = vmap (viter nine (+1)) (viter nine (+9) 1) Given a matrix of at least one row, we can compute its width: > width :: Vector (S n) (Vector m a) -> Nat m > width (VCons x xs) = len x > len :: Vector n a -> Nat n > len VNil = Z > len (VCons a x) = S (len x) The type LT is a GADT, indexed by a pair of type-level naturals. For given type-level naturals m and n, the type LT m n has a single inhabitant: a proof of the fact that m data LT m n where > Base :: LT Z (S n) > Step :: LT m n -> LT (S m) (S n) As well as the type Nat n, which has a single inhabitant n, we need a type BNat n of bounded naturals, which has inhabitants naturals less than n. These inhabitants are constructed from proofs of the inequality. > data BNat n where > Inject :: LT m n -> BNat n Now we can write the list indexing operation: > index :: BNat n -> Vector n a -> a > index (Inject Base) (VCons a x) = a > index (Inject (Step u)) (VCons a x) = index (Inject u) x We need to convince the type checker of various properties of naturals. These proofs take the form of simple coercions. Firstly, any natural n is less than its successor: > ordered :: Nat n -> LT n (S n) > ordered Z = Base > ordered (S n) = Step (ordered n) Secondly, successor increases: > weaken :: LT m n -> LT m (S n) > weaken Base = Base > weaken (Step u) = Step (weaken u) Finally, any type of bounded naturals can be injected into the next larger such type: > include :: BNat n -> BNat (S n) > include (Inject l) = Inject (weaken l) We will also need to reason about sums of type-level naturals. > data Sum m n p where > SumZ :: Sum Z n n > SumS :: Sum m n p -> Sum (S m) n (S p) For example (though we don't actually need this in what follows), sums allow us to propagate vector lengths through append. > vapp :: Sum m n p -> Vector m a -> Vector n a -> Vector p a > vapp SumZ VNil y = y > vapp (SumS u) (VCons a x) y = VCons a (vapp u x y) We will need two properties of sum. One is a kind of associativity: that if m+(1+n)=p then also (1+m)+n=p. > sumAssoc :: Sum m (S n) p -> Sum (S m) n p > sumAssoc SumZ = SumS SumZ > sumAssoc (SumS s) = SumS (sumAssoc s) The other need is to extract a proof that m

sumLT :: Sum m (S n) p -> LT m p > sumLT SumZ = Base > sumLT (SumS s) = Step (sumLT s) The final ingredient we will need for matrix transposition is to compute the vector of predecessors of a type-level natural. We do this using an accumulating parameter; preds' n m s produces the vector of n elements counting up from m, making use of the proof s that all these elements (and in particular the last) are less than an implicit bound b. > preds :: Nat n -> Vector n (BNat n) > preds b = preds' b Z SumZ > preds' :: Nat n -> Nat m -> Sum m n b -> Vector n (BNat b) > preds' Z m s = VNil > preds' (S n) m s > = VCons (Inject (sumLT s)) (preds' n (S m) (sumAssoc s)) (The definition we constructed of this function during the meeting did not work; this is thebest I have come up with. I'm rather unhappy with it, because it is somewhat inefficient: both sumLT and sumAssoc take time linear in the size of the proof s, which grows with each step. But I suspect that those type-level manipulations are all necessary, even though they are ultimately unproductive.) Given predecessors, we can at last generalize the approach we took for size three: tr3 = fork3 (Trip (fmap fst3) (fmap snd3) (fmap thd3)) The functions fst3, snd3, thd3 are the indexing operators for triples; we generate a vector of such operators for indexing a vector. > tr :: Vector (S n) (Vector (S m) a) -> Vector (S m) (Vector (S n) a) > tr x = vfork (vmap (vmap . index) (preds (width x))) x I have yet to check that the proof that tr is its own inverse goes through smoothly, though, because I think I have a better plan.