Weakening vinyl's RecAll constraint through entailment

In the vinyl library, there is a RecAll type family, which let's us ask that a partially applied constraint is true for every type in a type level list. For example, we can write this:

myShowFunc :: RecAll f rs Show => Rec f rs -> String

And that's all lovely. Now, if we have the constraint RecAll f rs c where c is unknown, and we know the cx entails dx (to borrow language from ekmett's contstraints package), how can we get RecAll f rs d ?

The reason I ask is that I'm working with records in some functions that require satisfying several typeclass constraints. To do this, I am using the :&: combinator from the Control.Constraints.Combine module in the exists package. (Note: the package won't build if you have other things installed because it depends on a super-old version of contravariant . You can just copy the one module I mentioned though.) With this, I can get some really beautiful constraints while minimizing typeclass broilerplate. For example:

RecAll f rs (TypeableKey :&: FromJSON :&: TypeableVal) => Rec f rs -> Value

But, inside the body of this function, I call another function that demands a weaker constraint. It might look like this:

RecAll f rs (TypeableKey :&: TypeableVal) => Rec f rs -> Value

GHC can't see that the second statement follows from the first. I assumed that would be the case. What I can't see is how to prove it for reify it and help GHC out. So far, I've got this:

import Data.Constraint

weakenAnd1 :: ((a :&: b) c) :- a c                                                                    
weakenAnd1 = Sub Dict -- not the Dict from vinyl. ekmett's Dict.

weakenAnd2 :: ((a :&: b) c) :- b c                                                                    
weakenAnd2 = Sub Dict

These work fine. But this is where I am stuck:

-- The two Proxy args are to stop GHC from complaining about AmbiguousTypes
weakenRecAll :: Proxy f -> Proxy rs -> (a c :- b c) -> (RecAll f rs a :- RecAll f rs b)
weakenRecAll _ _ (Sub Dict) = Sub Dict

This does not compile. Is anyone aware of a way to get the effect I am looking for. Here are the errors if they are helpful. Also, I have Dict as a qualified import in my actual code, so that's why it mentions Constraint.Dict :

Table.hs:76:23:
    Could not deduce (a c) arising from a pattern
    Relevant bindings include
      weakenRecAll :: Proxy f
                      -> Proxy rs -> (a c :- b c) -> RecAll f rs a :- RecAll f rs b
        (bound at Table.hs:76:1)
    In the pattern: Constraint.Dict
    In the pattern: Sub Constraint.Dict
    In an equation for ‘weakenRecAll’:
        weakenRecAll _ _ (Sub Constraint.Dict) = Sub Constraint.Dict

Table.hs:76:46:
    Could not deduce (RecAll f rs b)
      arising from a use of ‘Constraint.Dict’
    from the context (b c)
      bound by a pattern with constructor
                 Constraint.Dict :: forall (a :: Constraint).
                                    (a) =>
                                    Constraint.Dict a,
               in an equation for ‘weakenRecAll’
      at Table.hs:76:23-37
    or from (RecAll f rs a)
      bound by a type expected by the context:
                 (RecAll f rs a) => Constraint.Dict (RecAll f rs b)
      at Table.hs:76:42-60
    Relevant bindings include
      weakenRecAll :: Proxy f
                      -> Proxy rs -> (a c :- b c) -> RecAll f rs a :- RecAll f rs b
        (bound at Table.hs:76:1)
    In the first argument of ‘Sub’, namely ‘Constraint.Dict’
    In the expression: Sub Constraint.Dict
    In an equation for ‘weakenRecAll’:
        weakenRecAll _ _ (Sub Constraint.Dict) = Sub Constraint.Dict

Let's begin by reviewing how Dict and (:-) are meant to be used.

ordToEq :: Dict (Ord a) -> Dict (Eq a)
ordToEq Dict = Dict

Pattern matching on a value Dict of type Dict (Ord a) brings the constraint Ord a into scope, from which Eq a can be deduced (because Eq is a superclass of Ord ), so Dict :: Dict (Eq a) is well-typed.

ordEntailsEq :: Ord a :- Eq a
ordEntailsEq = Sub Dict

Similarly, Sub brings its input constraint into scope for the duration of its argument, allowing this Dict :: Dict (Eq a) to be well-typed as well.

However, whereas pattern-matching on Dict brings a constraint into scope, pattern-matching on Sub Dict does not bring into scope some new constraint conversion rule. In fact, unless the input constraint is already in scope, you can't pattern-match on Sub Dict at all.

-- Could not deduce (Ord a) arising from a pattern
constZero :: Ord a :- Eq a -> Int
constZero (Sub Dict) = 0

-- okay
constZero' :: Ord a => Ord a :- Eq a -> Int
constZero' (Sub Dict) = 0

So that explains your first type error, "Could not deduce (ac) arising from a pattern" : you have tried to pattern-match on Sub Dict , but the input constraint ac was not already in scope.

The other type error, of course, is saying that the constraints you did manage to get into scope were not sufficient to satisfy the RecAll f rs b constraint. So, which pieces are needed, and which ones are missing? Let's look at the definition of RecAll .

type family RecAll f rs c :: Constraint where
  RecAll f [] c = ()     
  RecAll f (r : rs) c = (c (f r), RecAll f rs c)

Aha! RecAll is a type family, so unevaluated as it is, with a completely abstract rs , the constraint RecAll f rs c is a black box which could not be satisfied from any set of smaller pieces. Once we specialize rs to [] or (r : rs) , it becomes clear which pieces we need:

recAllNil :: Dict (RecAll f '[] c)
recAllNil = Dict

recAllCons :: p rs
           -> Dict (c (f r))
           -> Dict (RecAll f rs c)
           -> Dict (RecAll f (r ': rs) c)
recAllCons _ Dict Dict = Dict

I'm using p rs instead of Proxy rs because it's more flexible: if I had a Rec f rs , for instance I could use that as my proxy with p ~ Rec f .

Next, let's implement a version of the above with (:-) instead of Dict :

weakenNil :: RecAll f '[] c1 :- RecAll f '[] c2
weakenNil = Sub Dict

weakenCons :: p rs
           -> c1 (f r) :- c2 (f r)
           -> RecAll f rs c1 :- RecAll f rs c2
           -> RecAll f (r ': rs) c1 :- RecAll f (r ': rs) c2
weakenCons _ entailsF entailsR = Sub $ case (entailsF, entailsR) of
    (Sub Dict, Sub Dict) -> Dict

Sub brings its input constraint RecAll f (r ': rs) c1 into scope for the duration of its argument, which we've arranged to include the rest of the function's body. The equation for the type family RecAll f (r ': rs) c1 expands to (c1 (fr), RecAll f rs c1) , which are thus both brought into scope as well. The fact that they are in scope allows us to pattern-match on the two Sub Dict , and those two Dict bring their respective constraints into scope: c2 (fr) , and RecAll f rs c2 . Those two are precisely what the target constraint RecAll f (r ': rs) c2 expands to, so our Dict right-hand side is well-typed.

To complete our implementation of weakenAllRec , we will need to pattern-match on rs in order to determine whether to delegate the work to weakenNil or weakenCons . But since rs is at the type level, we cannot pattern-match on it directly. The Hasochism paper explains how in order to pattern-match on a type-level Nat , we need to create a wrapper datatype Natty . The way in which Natty works is that each of its constructors is indexed by a corresponding Nat constructor, so when we pattern-match on a Natty constructor at the value level, the corresponding constructor is implied at the type level as well. We could define such a wrapper for type-level lists such as rs , but it just so happens that Rec f rs already has constructors corresponding to [] and (:) , and the callers of weakenAllRec are likely to have one lying around anyway.

weakenRecAll :: Rec f rs
             -> (forall a. c1 a :- c2 a)
             -> RecAll f rs c1 :- RecAll f rs c2
weakenRecAll RNil       entails = weakenNil
weakenRecAll (fx :& rs) entails = weakenCons rs entails
                                $ weakenRecAll rs entails

Note that the type of entails must be forall a. c1 a :- c2 a forall a. c1 a :- c2 a , not merely c1 a :- c2 a , because we don't want to claim that weakenRecAll will work for any a of the caller's choosing, but rather, we want to require the caller to prove that c1 a entails c2 a for every a .

链接地址: http://www.djcxy.com/p/33212.html

上一篇: Haskell:打印TextEncoding

下一篇: 通过包容来减弱乙烯基的RecAll约束