How to put constraints on type variable of kind `Constraint`?
The new QuantifiedConstraints
extension allows this.
class (a => b) => Implies a b where
instance (a => b) => Implies a b where
instance (forall a. c a `Implies` Show a) => Show (Some c) where
show (Some x) = show x
Within the body of the Show
instance, it is as if there is a
instance forall a. Implies (c a) (Show a)
in scope. If you then have T :: Type
and know c T
, then the superclass of c T => Show T
of the specialized Implies (c T) (Show T)
instance allows you to derive Show T
. It is necessary to use Implies
instead of a straight forall a. c a => Show a
constraint. This incorrect constraint acts like
instance forall a. c a => Show a
which overlaps with every Show
instance, causing weird breakage. Forcing an indirection through the superclass of an otherwise useless constraint fixes everything.
The closest we are able to get is a Class1
class that reifys the relationship between a class and a single superclass constraint as a class. It's based on the Class
from constraints.
First, we'll take a short tour of the constraints package. A Dict
captures the dictionary for a Constraint
data Dict :: Constraint -> * where
Dict :: a => Dict a
:-
captures that one constraint entails another. If we have a :- b
, whenever we have the constraint a
we can produce the dictionary for the constraint b
.
newtype a :- b = Sub (a => Dict b)
We need a proof similar to :-
, we need to know that forall a. h a :- b a
, or h a => Dict (b a)
.
Single Inheritance
Actually implementing this for class
es with just single inheritance requires the kitchen sink of language extensions, including OverlappingInstances
.
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverlappingInstances #-}
import Data.Constraint
We'll define the class of constraints of kind k -> Constraint
where the constraint has a single superclass.
class Class1 b h | h -> b where
cls1 :: h a :- b a
We're now equipped to tackle our example problem. We have a class A
that requires a Show
instance.
class Show a => A a
instance A Int
Show
is a superclass of A
instance Class1 Show A where
cls1 = Sub Dict
We want to write Show
instances for Some
data Some (c :: * -> Constraint) where
Some :: c a => a -> Some c
We can Show
a Some Show
.
instance Show (Some Show) where
showsPrec x (Some a) = showsPrec x a
We can Show
a Some h
whenever h
has a single superclass b
and we could show Some b
.
instance (Show (Some b), Class1 b h) => Show (Some h) where
showsPrec x (Some (a :: a)) =
case cls1 :: h a :- b a of
Sub Dict -> showsPrec x ((Some a) :: Some b)
This lets us write
x :: Some A
x = Some (1 :: Int)
main = print x