Search code examples
haskellconstraintscombinators

Combining constraints?


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.


Solution

  • 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