I have the following GADT
data Vec n a where
T :: Vec VZero a
(:.) :: (VNat n) => a -> (Vec n a) -> (Vec (VSucc n) a)
to model fixed length vectors, using the class
class VNat n
data VZero
instance VNat VZero
data VSucc n
instance (VNat n) => VNat (VSucc n)
I tried to programm an append-function on vectors:
vAppend :: Vec n b -> Vec m b -> Vec nm b
vAppend T T = T -- nonsense, -- its just a minimal def for testing purposes
The type checker doesnt like it:
Could not deduce (nm ~ VZero)
from the context (n ~ VZero)
bound by a pattern with constructor
T :: forall a. Vec VZero a,
in an equation for `vAppend'
at VArrow.hs:20:9
or from (m ~ VZero)
bound by a pattern with constructor
T :: forall a. Vec VZero a,
in an equation for `vAppend'
at VArrow.hs:20:11
`nm' is a rigid type variable bound by
the type signature for vAppend :: Vec n b -> Vec m b -> Vec nm b
at VArrow.hs:20:1
Expected type: Vec nm b
Actual type: Vec VZero b
In the expression: T
In an equation for `vAppend': vAppend T T = T
Failed, modules loaded: Vectors.
Can anyone explain GHCs problems with the type variable nm? And what exactly means the ~ in the error message?
As it stands you are saying you can get a vector of any length by appending any two vectors. If you scrap the signature ghc infers that vAppend is supposed to yield vector of length VZero given any two vectors -- that makes sense but isn't what you want. You need a Plus type associated with your VNats to constrain the result type of vAppend on vectors. The natural way would be a type family of some sort, but I couldn't get it under the VNat class. In any case, this whole sort of idea is much more clearly realized with the promoted types of the DataKinds extension (in ghc-7.4 and later) -- though maybe you were deliberately trying to avoid that extension? This gets rid of the obnoxious unclosed character of VSucc, which admits VSucc Char etc. If you weren't trying to avoid DataKinds, then your module might look something like this:
{-#LANGUAGE GADTs, TypeFamilies, DataKinds, TypeOperators#-}
data Vec n a where -- or: data Vec :: VNat -> * -> * where
T :: Vec VZero a
(:.) :: a -> Vec n a -> Vec (VSucc n) a
-- no class constraint
data VNat = VZero | VSucc VNat -- standard naturals
type family n :+ m :: VNat -- note the kind of a ":+" is a promoted VNat
type instance VZero :+ n = n
type instance VSucc n :+ m = VSucc (n :+ m)
vAppend :: Vec n b -> Vec m b -> Vec (n :+ m) b
vAppend T v = v
vAppend (a :. u) v = a :. (vAppend u v)
Edit: It occurs to me, looking at this, that the line for the Plus- or :+- type family should have explicitly constrained the kinds of the arguments
type family (n::VNat) :+ (m::VNat) :: VNat
to keep out garbage types like Char :+ VZero and so on -- that is, on the same principle used to prefer DataKinds to types like data VSucc n. Also, then we can see that the two instances specify it completely, though I don't know how much use the compiler can make of this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With