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.