Copyright | (C) 2011-2014 Edward Kmett, |
---|---|
License | BSD-style (see the file LICENSE) |
Maintainer | Edward Kmett <ekmett@gmail.com> |
Stability | experimental |
Portability | non-portable |
Safe Haskell | Trustworthy |
Language | Haskell2010 |
ConstraintKinds
made type classes into types of a new kind, Constraint
.
Eq
:: * ->Constraint
Ord
:: * ->Constraint
Monad
:: (* -> *) ->Constraint
The need for this extension was first publicized in the paper
Scrap your boilerplate with class: extensible generic functions
by Ralf Lämmel and Simon Peyton Jones in 2005, which shoehorned all the
things they needed into a custom Sat
typeclass.
With ConstraintKinds
we can put into code a lot of tools for manipulating
these new types without such awkward workarounds.
- data Constraint :: BOX
- data Dict :: Constraint -> * where
- newtype a :- b = Sub (a => Dict b)
- (\\) :: a => (b => r) -> (a :- b) -> r
- weaken1 :: (a, b) :- a
- weaken2 :: (a, b) :- b
- contract :: a :- (a, a)
- (&&&) :: (a :- b) -> (a :- c) -> a :- (b, c)
- (***) :: (a :- b) -> (c :- d) -> (a, c) :- (b, d)
- trans :: (b :- c) -> (a :- b) -> a :- c
- refl :: a :- a
- top :: a :- ()
- bottom :: (() ~ Bool) :- c
- mapDict :: (a :- b) -> Dict a -> Dict b
- unmapDict :: (Dict a -> Dict b) -> a :- b
- class Class b h | h -> b where
- class b :=> h | h -> b where
The Kind of Constraints
data Constraint :: BOX
Category Constraint (:-) | Possible since GHC 7.8, when |
Typeable ((* -> *) -> Constraint) Alternative | |
Typeable ((* -> *) -> Constraint) Applicative | |
Typeable (* -> Constraint) Monoid | |
Typeable (Constraint -> Constraint -> *) (:-) | |
Typeable (Constraint -> *) Dict |
Dictionary
data Dict :: Constraint -> * where Source
Values of type
capture a dictionary for a constraint of type Dict
pp
.
e.g.
Dict
::Dict
(Eq
Int
)
captures a dictionary that proves we have an:
instance Eq
'Int
Pattern matching on the Dict
constructor will bring this instance into scope.
a :=> (Monoid (Dict a)) | |
a :=> (Read (Dict a)) | |
a :=> (Bounded (Dict a)) | |
a :=> (Enum (Dict a)) | |
() :=> (Eq (Dict a)) | |
() :=> (Ord (Dict a)) | |
() :=> (Show (Dict a)) | |
a => Bounded (Dict a) | |
a => Enum (Dict a) | |
Eq (Dict a) | |
(Typeable Constraint p, p) => Data (Dict p) | |
Ord (Dict a) | |
a => Read (Dict a) | |
Show (Dict a) | |
a => Monoid (Dict a) | |
Typeable (Constraint -> *) Dict |
Entailment
newtype a :- b infixr 9 Source
This is the type of entailment.
a
is read as :-
ba
"entails" b
.
With this we can actually build a category for Constraint
resolution.
e.g.
Because
is a superclass of Eq
a
, we can show that Ord
a
entails Ord
a
.Eq
a
Because instance
exists, we can show that Ord
a => Ord
[a]
entails Ord
a
as well.Ord
[a]
This relationship is captured in the :-
entailment type here.
Since p
and entailment composes, :-
p:-
forms the arrows of a Category
of constraints. However, Category
only because 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
, 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".Ord
a :-
Eq
[a]
What are the two ways?
Well, we can go from
via the
superclass relationship, and them from Ord
a :-
Eq
a
via the
instance, or we can go from Eq
a :-
Eq
[a]
via the instance
then from Ord
a :-
Ord
[a]
through the superclass relationship
and this diagram by definition must "commute".Ord
[a] :-
Eq
[a]
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.
Category Constraint (:-) | Possible since GHC 7.8, when |
() :=> (Eq ((:-) a b)) | |
() :=> (Ord ((:-) a b)) | |
() :=> (Show ((:-) a b)) | |
Eq ((:-) a b) | Assumes |
(Typeable Constraint p, Typeable Constraint q, p, q) => Data ((:-) p q) | |
Ord ((:-) a b) | Assumes |
Show ((:-) a b) | |
Typeable (Constraint -> Constraint -> *) (:-) |
(\\) :: a => (b => r) -> (a :- b) -> r infixl 1 Source
Given that a :- b
, derive something that needs a context b
, using the context a
Weakening a constraint product
The category of constraints is Cartesian. We can forget information.
Weakening a constraint product
The category of constraints is Cartesian. We can forget information.
contract :: a :- (a, a) Source
Contracting a constraint / diagonal morphism
The category of constraints is Cartesian. We can reuse information.
(&&&) :: (a :- b) -> (a :- c) -> a :- (b, c) Source
Constraint product
trans weaken1 (f &&& g) = f trans weaken2 (f &&& g) = g
(***) :: (a :- b) -> (c :- d) -> (a, c) :- (b, d) Source
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.
Every constraint implies truth
These are the terminal arrows of the category, and ()
is the terminal object.
Given any constraint there is a unique entailment of the ()
constraint from that constraint.
bottom :: (() ~ Bool) :- c Source
A bad type coercion lets you derive any constraint you want.
These are the initial arrows of the category and (() ~ Bool)
is the initial object
This demonstrates the law of classical logic "ex falso quodlibet"
Dict is fully faithful
unmapDict :: (Dict a -> Dict b) -> a :- b Source
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.
Reflection
class Class b h | h -> b where Source
Reify the relationship between a class and its superclass constraints as a class
Given a definition such as
class Foo a => Bar a
you can capture the relationship between 'Bar a' and its superclass 'Foo a' with
instanceClass
(Foo a) (Bar a) wherecls
=Sub
Dict
Now the user can use 'cls :: Bar a :- Foo a'
Class () () | |
Class () (Bounded a) | |
Class () (Enum a) | |
Class () (Eq a) | |
Class () (Monad f) | |
Class () (Functor f) | |
Class () (Num a) | |
Class () (Read a) | |
Class () (Show a) | |
Class () (Monoid a) | |
Class b a => () :=> (Class b a) | |
Class () ((:=>) b a) | |
Class () (Class b a) | |
Class (Eq a) (Ord a) | |
Class (Fractional a) (Floating a) | |
Class (Monad f) (MonadPlus f) | |
Class (Functor f) (Applicative f) | |
Class (Num a) (Fractional a) | |
Class (Applicative f) (Alternative f) | |
Class (Num a, Ord a) (Real a) | |
Class (Real a, Fractional a) (RealFrac a) | |
Class (Real a, Enum a) (Integral a) | |
Class (RealFrac a, Floating a) (RealFloat a) |
class b :=> h | h -> b where infixr 9 Source
Reify the relationship between an instance head and its body as a class
Given a definition such as
instance Foo a => Foo [a]
you can capture the relationship between the instance head and its body with
instance Foo a:=>
Foo [a] whereins
=Sub
Dict