I have a bunch of functions that work on Vectors, i.e. lists with type-enforced lengths.
I'm trying to make my types easier to write, i.e. instead of writing
foo :: (Fold Integer v, Map Integer Integer v v, ...) => ...
I'm declaring a new class NList
so I can just write foo :: NList v Integer => ...
The (simplified) class looks like this:
class ( Fold (v i) i
, Map i i (v i) (v i)
, Map i (Maybe i) (v i) (v (Maybe i))
) => NList v i
As you can see, I have to keep the "vector" type separate from the "item" type (i.e. v
separate from i
) so that I can do things like Map
onto a Maybe
vector.
As such, v
must have kind * -> *
, and i
kind *
.
However, when I try to instantiate it with vectors like so:
instance NList Vec2 Integer
instance NList Vec3 Integer
...
I get the following error:
Type synonym `Vec2' should have 1 argument, but has been given none
In the instance declaration for `NList Vec2 Integer'
Now, I'm very new to type-level programming, and I understand that I'm likely doing this in a very backward fashion. Is it possible to instantiate a type synonym like this? Do any type-wizards have suggestions for better ways to accomplish my goals?
The problem here is that Vec2
and Vec3
are presumably declared as something like this:
type Vec2 a = Vec (S (S Z)) a
type Vec3 a = Vec (S (S (S Z))) a
Type synonyms cannot be partially-applied, for various arcane reasons (they would lead to type-level lambdas, which wreck havoc with all sorts of things related to instance resolution and inference — imagine if you could define type Id a = a
and make it an instance of Monad
).
That is to say, you can't make Vec2
an instance of anything, because you can't use Vec2
as if it was a fully-fledge type constructor with the kind * -> *
; it's effectively a type-level "macro" that can only be fully applied.
However, you can define type synonyms as partial applications themselves:
type Vec2 = Vec (S (S Z))
type Vec3 = Vec (S (S (S Z)))
These are equivalent, except that your instances will be permitted.
If your Vec3
type actually looks like
type Vec3 a = Cons a (Cons a (Cons a Nil)))
or similar, then you're out of luck; you'll have to use a newtype
wrapper if you want to give any instances. (On the other hand, you should be able to avoid defining instances directly on these types entirely by giving instances for Nil
and Cons
instead, allowing you to use Vec3
as an instance.)
Note that with GHC 7.4's new constraint kinds, you can avoid a separate type entirely, and simply define a constraint synonym:
type NList v i =
( Fold (v i) i
, Map i i (v i) (v i)
, Map i (Maybe i) (v i) (v (Maybe i))
)
As far as your approach in general goes, it should basically work fine; the same general idea is used by the Vec package. The huge number of classes and large contexts can make error messages very confusing and slow down compilation, but such is the nature of type-level hackery. However, if you have a base Vec
type defined as the usual GADT:
data Vec n a where
Nil :: Vec Z a
Succ :: a -> Vec n a -> Vec (S n) a
then you don't actually need any typeclasses at all. If it's defined in some other manner but is still parametrised on a type-level natural, then you can replace all the classes with one:
data Proxy s = Proxy
class Nat n where
natElim
:: ((n ~ Z) => r)
-> (forall m. (n ~ S m, Nat m) => Proxy m -> r)
-> Proxy n
-> r
This should allow you to eliminate the contexts altogether, but makes defining the operations on the vectors a bit trickier.
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