I\'m playing around with the ConstraintKinds
extension of GHC.
I have the following data type, which is just a box for things fulfilling some one parameter constrai
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.
You cannot make Some c
an instance of Show
, except trivially.
You want to show
the a
inside Some
, but that variable is existentially quantified, so we cannot depend on any knowledge of the type of a
. In particular, we have no way of knowing that a
is an instance of Show
.
EDIT: I'll expand on my answer.
Even with more machinery, and giving up on a Show
instance, I still don't think what you want is possible because of the existential quantification.
First I'll rewrite Some
in a more familiar form
data Dict p where
Dict :: p a => a -> Dict p
The usual way to talk about "constraints implying constraints" is with the concept of constraint entailment.
data p :- q where
Sub :: p a => Dict q -> p :- q
We can think about a value of type p :- q
as a proof that if the constraint forall a. p a
holds, then forall a. q a
follows.
Now we try to write a sensible show
-ish function
showD :: p :- Show -> Dict p -> String
showD (Sub (Dict a)) (Dict b) = show b
At a glance, this might work. We have brought the following constraints into scope (forgive the pseudo-exists
syntax)
(0) p :: * -> Constraint
(1) exists a. p a -- (Dict p)
(2) exists b. p b => Show b -- (p :- Show)
But now things fall apart, GHC rightfully complains:
main.hs:10:33:
Could not deduce (Show a2) arising from a use of `show'
from the context (p a)
bound by a pattern with constructor
Sub :: forall (p :: * -> Constraint) (q :: * -> Constraint) a.
(p a) =>
Dict q -> p :- q,
in an equation for `showD'
at main.hs:10:8-19
or from (Show a1)
bound by a pattern with constructor
Dict :: forall (p :: * -> Constraint) a. (p a) => a -> Dict p,
in an equation for `showD'
at main.hs:10:13-18
or from (p a2)
bound by a pattern with constructor
Dict :: forall (p :: * -> Constraint) a. (p a) => a -> Dict p,
in an equation for `showD'
at main.hs:10:23-28
because it is impossible to unify the a
from (1)
with the b
from (2)
.
This is the same essential idea that is used throughout the constraints
package mentioned in the comments.
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)
.
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