How to put constraints on type variable of kind `Constraint`?

前端 未结 3 2270
北海茫月
北海茫月 2021-02-19 05:07

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

相关标签:
3条回答
  • 2021-02-19 05:59

    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.

    0 讨论(0)
  • 2021-02-19 06:02

    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.

    0 讨论(0)
  • 2021-02-19 06:11

    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 classes 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
    
    0 讨论(0)
提交回复
热议问题