I'm looking for a way to combine two (or more) constraints such that Combine c1 c2 a
implies both c1 a
and c2 a
, and vice versa. This could be useful when constraints are used as input:
data HList constraint where
Empty :: HList constraint
(:*:) :: constraint a => a -> HList constraint -> HList constraint
Where show
and negate
could be applied to elements of an hlist typed HList (Combine Show Num)
.
Having these requirements, type Combine c1 c2 a = (c1 a, c2 a)
won't do as type synonyms must be saturated. I as well tried declaring Combine
as a class but couldn't imply that (Combine c1 c2 a) => c1 a
or c2 a
:
class (c1 a, c2 a) => Combine c1 c2 a
instance (c1 a, c2 a) => Combine c1 c2 a
instance Combine c1 c2 a => c1 a -- error: Illegal head of an instance declaration
instance Combine c1 c2 a => c2 a -- error: Illegal head of an instance declaration
I wonder if this is truly possible, or else what are the good workarounds.
The fact that Combine c1 c2 a
implies c1 a
and c2 a
is already contained in the declaration
class (c1 a, c2 a) => Combine c1 c2 a
That's just how superclasses work! This is a feature you've definitely seen before.
-- Ord is declared "class Eq a => Ord a", therefore this works
eqOrd :: Ord a => a -> a -> Bool
eqOrd = (==) -- want Eq a, have Ord a, superclass constraint says this is enough
So everything you want Combine
to do is accomplished by writing simply
class (c1 a, c2 a) => Combine c1 c2 a
instance (c1 a, c2 a) => Combine c1 c2 a
E.g. the following typechecks
unzipCon :: HList (Combine c1 c2) -> (HList c1, HList c2)
unzipCon Empty = (Empty, Empty)
unzipCon (x :*: xs) = (x :*: xs1, x :*: xs2)
where (xs1, xs2) = unzipCon xs