haskell - Weakening vinyl's RecAll constraint through entailment -


in vinyl library, there recall type family, let's ask partially applied constraint true every type in type level list. example, can write this:

myshowfunc :: recall f rs show => rec f rs -> string 

and that's lovely. now, if have constraint recall f rs c c unknown, , know c x entails d x (to borrow language ekmett's contstraints package), how can recall f rs d?

the reason ask i'm working records in functions require satisfying several typeclass constraints. this, using :&: combinator control.constraints.combine module in exists package. (note: package won't build if have other things installed because depends on super-old version of contravariant. can copy 1 module mentioned though.) this, can beautiful constraints while minimizing typeclass broilerplate. example:

recall f rs (typeablekey :&: fromjson :&: typeableval) => rec f rs -> value 

but, inside body of function, call function demands weaker constraint. might this:

recall f rs (typeablekey :&: typeableval) => rec f rs -> value 

ghc can't see second statement follows first. assumed case. can't see how prove reify , ghc out. far, i've got this:

import data.constraint  weakenand1 :: ((a :&: b) c) :- c                                                                     weakenand1 = sub dict -- not dict vinyl. ekmett's dict.  weakenand2 :: ((a :&: b) c) :- b c                                                                     weakenand2 = sub dict 

these work fine. stuck:

-- 2 proxy args stop ghc complaining ambiguoustypes weakenrecall :: proxy f -> proxy rs -> (a c :- b c) -> (recall f rs :- recall f rs b) weakenrecall _ _ (sub dict) = sub dict 

this not compile. aware of way effect looking for. here errors if helpful. also, have dict qualified import in actual code, that's why mentions constraint.dict:

table.hs:76:23:     not deduce (a c) arising pattern     relevant bindings include       weakenrecall :: proxy f                       -> proxy rs -> (a c :- b c) -> recall f rs :- recall f rs b         (bound @ table.hs:76:1)     in pattern: constraint.dict     in pattern: sub constraint.dict     in equation ‘weakenrecall’:         weakenrecall _ _ (sub constraint.dict) = sub constraint.dict  table.hs:76:46:     not deduce (recall f rs b)       arising use of ‘constraint.dict’     context (b c)       bound pattern constructor                  constraint.dict :: forall (a :: constraint).                                     (a) =>                                     constraint.dict a,                in equation ‘weakenrecall’       @ table.hs:76:23-37     or (recall f rs a)       bound type expected context:                  (recall f rs a) => constraint.dict (recall f rs b)       @ table.hs:76:42-60     relevant bindings include       weakenrecall :: proxy f                       -> proxy rs -> (a c :- b c) -> recall f rs :- recall f rs b         (bound @ table.hs:76:1)     in first argument of ‘sub’, namely ‘constraint.dict’     in expression: sub constraint.dict     in equation ‘weakenrecall’:         weakenrecall _ _ (sub constraint.dict) = sub constraint.dict 

let's begin reviewing how dict , (:-) meant used.

ordtoeq :: dict (ord a) -> dict (eq a) ordtoeq dict = dict 

pattern matching on value dict of type dict (ord a) brings constraint ord a scope, eq a can deduced (because eq superclass of ord), dict :: dict (eq a) well-typed.

ordentailseq :: ord :- eq ordentailseq = sub dict 

similarly, sub brings input constraint scope duration of argument, allowing dict :: dict (eq a) well-typed well.

however, whereas pattern-matching on dict brings constraint scope, pattern-matching on sub dict not bring scope new constraint conversion rule. in fact, unless input constraint in scope, can't pattern-match on sub dict @ all.

-- not deduce (ord a) arising pattern constzero :: ord :- eq -> int constzero (sub dict) = 0  -- okay constzero' :: ord => ord :- eq -> int constzero' (sub dict) = 0 

so explains first type error, "could not deduce (a c) arising pattern": have tried pattern-match on sub dict, input constraint a c not in scope.

the other type error, of course, saying constraints did manage scope not sufficient satisfy recall f rs b constraint. so, pieces needed, , ones missing? let's @ definition of recall.

type family recall f rs c :: constraint   recall f [] c = ()        recall f (r : rs) c = (c (f r), recall f rs c) 

aha! recall type family, unevaluated is, abstract rs, constraint recall f rs c black box not satisfied set of smaller pieces. once specialize rs [] or (r : rs), becomes clear pieces 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 had rec f rs, instance use proxy p ~ rec f.

next, let's implement version of above (:-) 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 input constraint recall f (r ': rs) c1 scope duration of argument, we've arranged include rest of function's body. equation type family recall f (r ': rs) c1 expands (c1 (f r), recall f rs c1), both brought scope well. fact in scope allows pattern-match on 2 sub dict, , 2 dict bring respective constraints scope: c2 (f r), , recall f rs c2. 2 precisely target constraint recall f (r ': rs) c2 expands to, our dict right-hand side well-typed.

to complete our implementation of weakenallrec, need pattern-match on rs in order determine whether delegate work weakennil or weakencons. since rs @ type level, cannot pattern-match on directly. hasochism paper explains how in order pattern-match on type-level nat, need create wrapper datatype natty. way in natty works each of constructors indexed corresponding nat constructor, when pattern-match on natty constructor @ value level, corresponding constructor implied @ type level well. define such wrapper type-level lists such rs, happens rec f rs has constructors corresponding [] , (:), , callers of weakenallrec have 1 lying around anyway.

weakenrecall :: rec f rs              -> (forall a. c1 :- 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 type of entails must forall a. c1 :- c2 a, not merely c1 :- c2 a, because don't want claim weakenrecall work a of caller's choosing, rather, want require caller prove c1 a entails c2 a every a.