Skip to the content.

Implementing length-indexed vector using dependent types

In Implementing Maybe using dependent types, I implemented Maybe using dependent types. The type itself is Optional which is indexed by kind O. In kind O, there are two types S and N.

So it’s basically a type indexed by a kind with two types. Then, how does it look like if we implement a type indexed by a kind with infinite number of types? Let’s try it.

First, let’s define a kind which have infinite number of types.

{-# LANGUAGE DataKinds,
             GADTs,
             RankNTypes,
             StandaloneDeriving,
             StandaloneKindSignatures,
             TypeOperators
#-}

import Data.Kind (Type)
import Data.Type.Equality ((:~:)(Refl))

data Nat = Z | S Nat

type Nat0 = 'Z
type Nat1 = 'S 'Z
type Nat2 = 'S Nat1
type Nat3 = 'S Nat2

Nat kind has infinite number of types such as 'Z, 'S 'Z, 'S ('S 'Z) and so on.

We need a singleton type for it to use it as an index kind.

type SNat :: Nat -> Type
data SNat n where
    SZ :: SNat 'Z
    SS :: SNat m -> SNat ('S m)
deriving instance Show (SNat n)

Now that we have an index kind, let’s define a type indexed by this kind. This time, we’ll define a length-indexed vector.

type Vec :: Nat -> Type -> Type
data Vec len a where
    VCons :: a -> Vec len a -> Vec ('S len) a
    VNil :: Vec 'Z a
deriving instance Show a => Show (Vec len a)

For example, you can use it like this.

v :: Vec ('S ('S 'Z')) Int
v = VCons 1 (VCons 2 VNil)

We also define an existential type wrapping this type.

data SomeVec a = forall len. SomeVec (SNat len) (Vec len a)
deriving instance Show a => Show (SomeVec a)

The next thing we’d like to do is to constract it from an ordinal list.

vec :: [a] -> SomeVec a
vec [] = SomeVec SZ VNil
vec (x:xs) = case vec xs of
               SomeVec sLen v -> SomeVec (SS sLen) (VCons x v)

Now, we can define functions which have restrictions on length of vectors it takes as arguments. For example, we can define a function calculating sum of a vector whose length is 3.

sum3 :: Monoid a => Vec Nat3 a -> a
sum3 (VCons a1 (VCons a2 (VCons a3 VNil))) = a1 <> a2 <> a3

If you have an ordinal list, you con convert it to SomeVec and call sum3 only if its length is 3.

trySum3 :: Monoid a => [a] -> Maybe a
trySum3 xs = case vec xs of
               SomeVec (SS (SS (SS SZ))) v -> Just $ sum3 v
               _ -> Nothing

As you can see, if SNat len of SomeVec is SS (SS (SS SZ)), len must be 'S ('S ('S 'Z)), so you can pass it to sum3.

Let’s take a look at another example. This time, we’ll define a zip function which only works on two vectors of the same length.

vzip :: Vec len a -> Vec len b -> Vec len (a, b)
vzip VNil VNil = VNil
vzip (VCons x xs) (VCons y ys) = VCons (x, y) $ vzip xs ys

Let’s call this vzip with two ordinal lists. To do this, we need a function checking propositional equality of SNat. It returns Just Refl if both types are the same, and returns Nothing otherwise.

sameSNat :: SNat n -> SNat m -> Maybe (n :~: m)
sameSNat SZ SZ = Just Refl
sameSNat (SS sNat1) (SS sNat2) = case sameSNat sNat1 sNat2 of
                                   Just Refl -> Just Refl
                                   Nothing -> Nothing
sameSNat _ _ = Nothing

Now you can convert ordinal lists to SomeVecs and check if their length are equal using sameSNat, and call vzip if they are the same.

tryZip :: [a] -> [b] -> Maybe (SomeVec (a, b))
tryZip xs ys = case (vec xs, vec ys) of
                 (SomeVec sLenX vx, SomeVec sLenY vy) ->
                   case sameSNat sLenX sLenY of
                     Just Refl -> Just (SomeVec sLenX (vzip vx vy))
                     Nothing -> Nothing

As you can see, we extended null-safety to length-safety by introducing a length-indexed vector.