[Haskell-cafe] Re: Implementing fixed-sized vectors (using datatype algebra?)

Alfonso Acosta alfonso.acosta at gmail.com
Sat Feb 9 23:14:06 EST 2008

Moving on to the implementation of fixed-sized vectors themselves ...

I have been trying to implement them as a GADT but I have run into
quite few problems. As a result, I'm considering to implement them
using the more-traditional phantom type-parameter approach. Anyhow,
I'd like to share those problems with the list, just in case someone
comes with a solution.

Here are some examples of what I was able to define without problems
(although, for some cases, I was forced to "break" the safety layer of
the GADT by using the toInt reflection function).

Save this email as FSVec.lhs to test them.

> {-# LANGUAGE GADTs, Rank2Types, ScopedTypeVariables, KindSignatures #-}
> module Data.Param.FSVec where
> import Data.TypeLevel.Num

The Fixed Sized Vector data type. I know Wolfgang would prefer
something more closely named to LiSt to, but let's avoid getting into
that discussion now.

> data FSVec :: * -> * -> * where
>    NullV :: FSVec D0 a
>    (:>)  :: Succ s s' => a -> FSVec s a -> FSVec s' a

> infixr :>

Some successful examples

> headV :: Pos s => FSVec s a -> a
> headV (x :> xs) = x

> lastV :: Pos s => FSVec s a -> a
> lastV = lastV'
>   -- trusted function without the Pos constraint, otherwise the compiler would complain about
>   -- the Succ constraint of :> not being met.
>   where lastV' :: FSVec s a -> a
>         lastV' (x :> NullV) = x
>         lastV' (x :> xs)    = lastV' xs

> atV :: (Pos s, Nat n, n :<: s) => FSVec s a -> n -> a
> atV v n = atV' v (toInt n)
>   -- Reflecting the index breaks checking that the recursive call
>   -- verifies the atV constraints, however I couldn't find another way.
>   -- atV' is to be trusted regarding the recursive call
>   where atV' :: FSVec s a -> Int -> a
>         atV' (x :> xs) n
>          | n == 0       = x
>          | otherwise    = atV' xs (n-1)
> -- this defition is nicer but doesn't typecheck
> -- atV (x :> xs) n
> -- | toInt  n == 0 = x
> -- | otherwise = atV xs (predRef n)

Now some functions which I wasn't able to define

Concat function. This would be the naive implementation, but it fails
to compile.

(<+>) :: Add s1 s2 s3 => FSVec s1 a -> FSVec s2 a -> FSVec s3 a
NullV  <+> ys  = ys
(x:>xs) <+> ys = x :> (xs <+> ys)

Tail function, which is also incorrect.

tailV :: Succ s' s => FSVec s a -> FSVec s' a
tailV (x :> xs) = xs

And finally, vector, which is supposed to build a fixed-sized vector
out of a list.

The ideal type for the function would be:

vector :: [a] -> FSVec s a

But there is no apparent way in which to obtain s based on the length
of the input list.

[1] shows a way in which to create vector using CPS style and a
reification function:

reifyInt :: Int -> (forall s . Nat s => FSVect s a -> w) -> w

The result would be a function with the following type:

vector :: [a] -> (forall s . Nat s => FSVec s a -> w) -> w

Nevertheless, I'm not fully satisfied by it.

Another alternative would be forcing the user to provide the size
explicitly (which is ugly as well)

vector' :: Nat s => s -> [a] -> FSVec s a

The Succ constraint in the definition of :> doesn't allow me to do
such a thing. The following implementation does not typecheck:

vector' :: Nat s => s -> [a] -> FSVec s a
vector' s l
 | toInt s == length l = vector' l
 | otherwise           = error "dynamic/static size mismatch"
   where vector'' :: [a] -> FSVec s a
              vector'' []        = NullV
              vector'' (x : xs)  = x :> vector' xs

The problem is that I don't know a way in which to "bypass" the Succ
constraint of :> .

Using a traditional phantom type-parameter to express the size is the
only solution I can think of (which would also solve the problems of
init and tail).  However, that would mean losing the ability of
pattern matching with (:>).

Any comments/suggestions would be very much appreciated.



[1] http://ofb.net/~frederik/vectro/draft-r2.pdf

More information about the Haskell-Cafe mailing list