在vinyl库中,有一个RecAll
类型系列,让我们可以询问部分应用的约束是否适用于类型级别列表中的每个类型。例如,我们可以这样写:
myShowFunc :: RecAll f rs Show => Rec f rs -> String
这一切都很可爱。现在,如果我们有RecAll f rs c
约束,其中c
未知,我们知道c x
需要d x
(借用ekmett {{3}的语言} package,我们怎样才能获得RecAll f rs d
?
我问的原因是我在一些需要满足几个类型类约束的函数中使用记录。为此,我使用contstraints包中Control.Constraints.Combine模块的:&:
组合子。 (注意:如果你安装了其他东西,那么包不会构建,因为它取决于contravariant
的超旧版本。你可以复制我提到的那个模块。)有了这个,我可以得到一些非常美丽的约束,同时最小化类型类肉鸡。例如:
RecAll f rs (TypeableKey :&: FromJSON :&: TypeableVal) => Rec f rs -> Value
但是,在这个函数体内,我调用另一个需要较弱约束的函数。它可能看起来像这样:
RecAll f rs (TypeableKey :&: TypeableVal) => Rec f rs -> Value
GHC无法看到第二个声明来自第一个声明。我认为情况就是这样。我无法看到的是如何证明它以实现它并帮助GHC。到目前为止,我已经得到了这个:
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
这些工作正常。但这就是我陷入困境的地方:
-- 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
这不编译。是否有人知道如何获得我正在寻找的效果。如果它们有用,则会出现以下错误。另外,我在实际代码中将Dict
作为合格导入,这就是它提到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
答案 0 :(得分:12)
让我们首先回顾一下Dict
和(:-)
的使用方式。
ordToEq :: Dict (Ord a) -> Dict (Eq a)
ordToEq Dict = Dict
对Dict
类型的值Dict (Ord a)
进行模式匹配会将约束Ord a
纳入范围,从中可以推导出Eq a
(因为Eq
是一个Ord
的超类,所以Dict :: Dict (Eq a)
的类型很好。
ordEntailsEq :: Ord a :- Eq a
ordEntailsEq = Sub Dict
类似地,Sub
将其输入约束带入其参数持续时间的范围,允许此Dict :: Dict (Eq a)
也是良好类型的。
然而,虽然Dict
上的模式匹配会将约束带入范围,但Sub Dict
上的模式匹配并未将某些新的约束转换规则纳入范围。实际上,除非输入约束已经在范围内,否则根本不能在Sub Dict
上进行模式匹配。
-- 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
这样就解释了您的第一个类型错误"Could not deduce (a c) arising from a pattern"
:您尝试在Sub Dict
上进行模式匹配,但输入约束a c
尚未在范围内。
另一种类型的错误当然是说你设法进入范围的约束不足以满足RecAll f rs b
约束。那么,需要哪些部分,哪些部分丢失?我们来看看RecAll
。
type family RecAll f rs c :: Constraint where
RecAll f [] c = ()
RecAll f (r : rs) c = (c (f r), RecAll f rs c)
啊哈! RecAll
是一个类型系列,因此没有评估,具有完全抽象的rs
,约束RecAll f rs c
是一个黑盒子,无法从任何一组较小的片段中得到满足。一旦我们将rs
专门化为[]
或(r : rs)
,我们就会明白需要哪些内容:
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
我使用p rs
代替Proxy rs
,因为它更灵活:例如,如果我有Rec f rs
,我可以将其用作p ~ Rec f
的代理。
接下来,让我们使用(:-)
而不是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
将其输入约束RecAll f (r ': rs) c1
置于其参数持续时间的范围内,我们已将其安排为包含函数体的其余部分。类型族RecAll f (r ': rs) c1
的等式扩展为(c1 (f r), RecAll f rs c1)
,因此它们也被纳入范围。它们在范围内的事实允许我们在两个Sub Dict
上进行模式匹配,并且这两个Dict
将各自的约束纳入范围:c2 (f r)
和RecAll f rs c2
。这两个正是目标约束RecAll f (r ': rs) c2
扩展到的内容,因此我们的Dict
右侧是良好类型的。
要完成weakenAllRec
的实施,我们需要在rs
上进行模式匹配,以确定是否将工作委托给weakenNil
或weakenCons
。但由于rs
属于类型级别,因此我们无法直接对其进行模式匹配。 Hasochism论文解释了为了在类型级Nat
上进行模式匹配,我们需要创建一个包装器数据类型Natty
。 Natty
的工作方式是每个构造函数都由相应的Nat
构造函数索引,因此当我们在值Natty
构造函数上进行模式匹配时,相应的构造函数也暗示了类型级别。我们可以为类型级别列表定义这样的包装器,例如rs
,但恰好Rec f rs
已经具有与[]
和(:)
对应的构造函数,以及调用者无论如何,weakenAllRec
可能会有一个人躺着。
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
请注意,entails
的类型必须为forall a. c1 a :- c2 a
,而不仅仅是c1 a :- c2 a
,因为我们不希望声称weakenRecAll
适用于任何a
调用者选择的,但我们要求调用者证明c1 a
每c2 a
需要a
。