16
votes

I have the following definition of fixed-length-vectors using ghcs extensions GADTs, TypeOperators and DataKinds:

data Vec n a where
    T    :: Vec VZero a
    (:.) :: a -> Vec n a -> Vec (VSucc n) a 

infixr 3 :.

data VNat  =  VZero |  VSucc VNat  -- ... promoting Kind VNat

type T1 = VSucc VZero
type T2 = VSucc T1

and the following defiition of a TypeOperator :+:

type family (n::VNat) :+ (m::VNat) :: VNat 
type instance VZero :+ n = n
type instance VSucc n :+ m = VSucc (n :+ m)

For my whole intented library to make sense, I need to apply a fixed-length-vector-function of type (Vec n b)->(Vec m b) to the inial part of a longer vector Vec (n:+k) b. Let's call that function prefixApp. It should have type

prefixApp :: ((Vec n b)->(Vec m b)) -> (Vec (n:+k) b) -> (Vec (m:+k) b)

Here's an example application with the fixed-length-vector-function change2 defined like this:

change2 :: Vec T2 a -> Vec T2 a
change2 (x :. y :. T) = (y :. x :. T)

prefixApp should be able to apply change2 to the prefix of any vector of length >=2, e.g.

Vector> prefixApp change2 (1 :. 2 :. 3 :. 4:. T)
(2 :. 1 :. 3 :. 4 :. T)

Has anyone any idea how to implement prefixApp? (The problem is, that a part of the type of the fixed-length-vector-function has to be used to grab the prefix of the right size...)

Edit: Daniel Wagners (very clever!) solution seems to have worked with some release candidate of ghc 7.6 (not an official release!). IMHO it shouldnt work, however, for 2 reasons:

  1. The type-declaration for prefixApp lacks an VNum m in the context (for prepend (f b) to typecheck correctly.
  2. Even more problematic: ghc 7.4.2 does not assume the TypeOperator :+ to be injective in its first argument (nor the second, but thats not essential here), which leads to a type error: from the type-declaration, we know that vec must have type Vec (n:+k) a and the type-checker infers for the expression split vec on the right-hand side of the definition a type of Vec (n:+k0) a. But: the type-checker cannot infer that k ~ k0 (since there is no assurance that :+ is injective).

Does anyone know a solution to this second issue? How can I declare :+ to be injective in its first argument and/or how can I avoid running into this issue at all?

3
It seems like this should be possible with type classes and functional dependencies. Unfortunately, my prototype code seems to have arisen a bug in GHC, so I can't test it.gereeter
@gereeter: Yes, I agree that it should be (in theory!) be possible with functional dependencies, which was actually my first approach. Unfortunately, it didnt work for reasons which are not clear to me. Nevertheless, I would be interested in solution which uses functional dependencies...phynfo

3 Answers

7
votes

Here is a version where split is not in a type class. Here we build a singleton type for natural numbers (SN), which enables to pattern match on `n' in the definition of split'. This extra argument can then be hidden by the use of a type class (ToSN).

The type Tag is used to manually specify the non-inferred arguments.

(this answer has been co-authored with Daniel Gustafsson)

Here is the code:

{-# LANGUAGE TypeFamilies, TypeOperators, DataKinds, GADTs, ScopedTypeVariables, FlexibleContexts #-}
module Vec where
data VNat = VZero | VSucc VNat  -- ... promoting Kind VNat

data Vec n a where
    T    :: Vec VZero a
    (:.) :: a -> Vec n a -> Vec (VSucc n) a·

infixr 3 :.

type T1 = VSucc VZero
type T2 = VSucc T1

data Tag (n::VNat) = Tag

data SN (n::VNat) where
  Z :: SN VZero
  S :: SN n -> SN (VSucc n)

class ToSN (n::VNat) where
  toSN :: SN n

instance ToSN VZero where
  toSN = Z

instance ToSN n => ToSN (VSucc n) where
  toSN = S toSN

type family (n::VNat) :+ (m::VNat) :: VNat
type instance VZero :+ n = n
type instance VSucc n :+ m = VSucc (n :+ m)

split' :: SN n -> Tag m -> Vec (n :+ m) a -> (Vec n a, Vec m a)
split' Z     _ xs = (T , xs)
split' (S n) _ (x :. xs) = let (as , bs) = split' n Tag xs in (x :. as , bs)

split :: ToSN n => Tag m -> Vec (n :+ m) a -> (Vec n a, Vec m a)
split = split' toSN

append :: Vec n a -> Vec m a -> Vec (n :+ m) a
append T ys = ys
append (x :. xs) ys = x :. append xs ys

prefixChange :: forall a m n k. ToSN n => (Vec n a -> Vec m a) -> Vec (n :+ k) a -> Vec (m :+ k) a
prefixChange f xs = let (as , bs) = split (Tag :: Tag k) xs in append (f as) bs
7
votes

Make a class:

class VNum (n::VNat) where
    split   :: Vec (n:+m) a -> (Vec n a, Vec m a)
    prepend :: Vec n a -> Vec m a -> Vec (n:+m) a

instance VNum VZero where
    split     v = (T, v)
    prepend _ v = v

instance VNum n => VNum (VSucc n) where
    split   (x :. xs)   = case split xs of (b, e) -> (x :. b, e)
    prepend (x :. xs) v = x :. prepend xs v

prefixApp :: VNum n => (Vec n a -> Vec m a) -> (Vec (n:+k) a -> (Vec (m:+k) a))
prefixApp f vec = case split vec of (b, e) -> prepend (f b) e
4
votes

If you can live with a slightly different type of prefixApp:

{-# LANGUAGE GADTs, TypeOperators, DataKinds, TypeFamilies #-}

import qualified Data.Foldable as F


data VNat  =  VZero |  VSucc VNat  -- ... promoting Kind VNat

type T1 = VSucc VZero
type T2 = VSucc T1
type T3 = VSucc T2

type family (n :: VNat) :+ (m :: VNat) :: VNat
type instance VZero :+ n = n
type instance VSucc n :+ m = VSucc (n :+ m)

type family (n :: VNat) :- (m :: VNat) :: VNat
type instance n :- VZero = n
type instance VSucc n :- VSucc m = n :- m


data Vec n a where
    T    :: Vec VZero a
    (:.) :: a -> Vec n a -> Vec (VSucc n) a 

infixr 3 :.

-- Just to define Show for Vec
instance F.Foldable (Vec n) where
    foldr _ b T = b
    foldr f b (a :. as) = a `f` F.foldr f b as

instance Show a => Show (Vec n a) where
    show = show . F.foldr (:) []


class Splitable (n::VNat) where
    split :: Vec k b -> (Vec n b, Vec (k:-n) b)

instance Splitable VZero where
    split r = (T,r)

instance Splitable n => Splitable (VSucc n) where
    split (x :. xs) =
        let (xs' , rs) = split xs
        in ((x :. xs') , rs)

append :: Vec n a -> Vec m a -> Vec (n:+m) a
append T r = r
append (l :. ls) r = l :. append ls r

prefixApp :: Splitable n => (Vec n b -> Vec m b) -> Vec k b -> Vec (m:+(k:-n)) b
prefixApp f v = let (v',rs) = split v in append (f v') rs

-- A test
inp :: Vec (T2 :+ T3) Int
inp = 1 :. 2 :. 3 :. 4:. 5 :. T

change2 :: Vec T2 a -> Vec T2 a
change2 (x :. y :. T) = (y :. x :. T)

test = prefixApp change2 inp -- -> [2,1,3,4,5]

In fact, your original signature can also be used (with augmented context):

prefixApp :: (Splitable n, (m :+ k) ~ (m :+ ((n :+ k) :- n))) =>
             ((Vec n b)->(Vec m b)) -> (Vec (n:+k) b) -> (Vec (m:+k) b)
prefixApp f v = let (v',rs) = split v in append (f v') rs

Works in 7.4.1

Upd: Just for fun, the solution in Agda:

data Nat : Set where
  zero : Nat
  succ : Nat -> Nat

_+_ : Nat -> Nat -> Nat
zero + r = r
succ n + r = succ (n + r)

data _*_ (A B : Set) : Set where
  _,_ : A -> B -> A * B

data Vec (A : Set) : Nat -> Set where
  [] : Vec A zero
  _::_ : {n : Nat} -> A -> Vec A n -> Vec A (succ n)

split : {A : Set}{k n : Nat} -> Vec A (n + k) -> (Vec A n) * (Vec A k)
split {_} {_} {zero} v = ([] , v)
split {_} {_} {succ _} (h :: t) with split t
... | (l , r) = ((h :: l) , r)

append : {A : Set}{n m : Nat} -> Vec A n -> Vec A m -> Vec A (n + m)
append [] r = r
append (h :: t) r with append t r
... | tr = h :: tr

prefixApp : {A : Set}{n m k : Nat} -> (Vec A n -> Vec A m) -> Vec A (n + k) -> Vec A (m + k)
prefixApp f v with split v
... | (l , r) = append (f l) r