With this we can actually build a category for Constraint resolution.
e.g.
Because Eq a is a superclass of Ord a, we can show that Ord a
entails Eq a.
Because instance Ord a => Ord [a] exists, we can show that Ord a
entails Ord [a] as well.
This relationship is captured in the :- entailment type here.
Since p :- p and entailment composes, :- forms the arrows of a
Category of constraints. However, Category only became sufficiently
general to support this instance in GHC 7.8, so prior to 7.8 this instance
is unavailable.
But due to the coherence of instance resolution in Haskell, this Category
has some very interesting properties. Notably, in the absence of
IncoherentInstances, this category is "thin", which is to say that
between any two objects (constraints) there is at most one distinguishable
arrow.
This means that for instance, even though there are two ways to derive
Ord a :-Eq [a], the answers from these two paths _must_ by
construction be equal. This is a property that Haskell offers that is
pretty much unique in the space of languages with things they call "type
classes".
What are the two ways?
Well, we can go from Ord a :-Eq a via the
superclass relationship, and then from Eq a :-Eq [a] via the
instance, or we can go from Ord a :-Ord [a] via the instance
then from Ord [a] :-Eq [a] through the superclass relationship
and this diagram by definition must "commute".
Diagrammatically,
Ord a
ins / \ cls
v v
Ord [a] Eq a
cls \ / ins
v v
Eq [a]
This safety net ensures that pretty much anything you can write with this
library is sensible and can't break any assumptions on the behalf of
library authors.
due to the hack for the kind of (,) in the current version of GHC we can't actually
make instances for (,) :: Constraint -> Constraint -> Constraint, but we can define
an equivalent type, that converts back and forth to (,), and lets you hang instances.
Instances1&
(p, q) => p&qDefined in constraints-0.14.2 · Data.Constraint
due to the hack for the kind of (,) in the current version of GHC we can't actually
make instances for (,) :: Constraint -> Constraint -> Constraint, but (,) is a
bifunctor on the category of constraints. This lets us map over both sides.
From a category theoretic perspective Dict is a functor that maps from the category
of constraints (with arrows in :-) to the category Hask of Haskell data types.
This functor is fully faithful, which is to say that given any function you can write
Dict a -> Dict b there also exists an entailment a :- b in the category of constraints
that you can build.