作为简介,请参阅this question和my answer。我最后注意到,您似乎可以消除对具有功能依赖性的无关类型规范的需求。这是代码:
{-# LANGUAGE GADTs, MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies #-}
data Nil
data TList a b where
TEmpty :: TList Nil Nil
(:.) :: c -> TList d e -> TList c (TList d e)
infixr 4 :.
class TApplies f h t r where
tApply :: f -> TList h t -> r
instance TApplies a Nil Nil a where
tApply a _ = a
instance TApplies f h t r => TApplies (a -> f) a (TList h t) r where
tApply f (e :. l) = tApply (f e) l]
现在,我要做的直观事情似乎是将fundep f h t -> r
添加到
TApplies
类型类。但是,当我这样做时,GHC抱怨TApplies
的递归实例如下:
Illegal instance declaration for
‘TApplies (a -> f) a (TList h t) r’
The coverage condition fails in class ‘TApplies’
for functional dependency: ‘f h t -> r’
Reason: lhs types ‘a -> f’, ‘a’, ‘TList h t’
do not jointly determine rhs type ‘r’
最后两行对我来说似乎不对,虽然我希望我只是误解了一些东西。我的推理如下:
a -> f
和a
,那么我们有f
。TList h t
,那么我们会h
和t
。TApplies f h t r
,我们有fundep f h t -> r
。f
,h
和t
,我们有r
。这对我来说意味着功能依赖 是满足的。有人可以指出我的逻辑中的缺陷,也可能建议更合适的功能依赖选择? (请注意,GHC允许使用类似f r -> h
的内容,但似乎没有为类型指示提供太多余地。)
答案 0 :(得分:4)
首先,我不知道你为什么要使用这么复杂的数据类型 - 所以我会用一个简化的数据来解决你的问题。但是,同样的原则适用于您的确切情况。
将数据类型简单定义为:
data TList (xs :: [*]) where
Nil :: TList '[]
(:.) :: x -> TList xs -> TList (x ': xs)
infixr 4 :.
然后你的类只有一个类型参数:
class TApplies f (xs :: [*]) r | f xs -> r where ...
并且有问题的实例看起来像
instance TApplies g ys q => TApplies (y -> g) (y ': ys) q where
现在,检查一下它产生的错误(它与你的问题基本相同) - 即,注意它说“覆盖条件在类'TApplies'中失败”。那么这个“覆盖条件”是什么? user guide可以这样说:
覆盖条件。对于每个功能依赖,tvsleft - > TVsright,该类,S(tvsright)中的每个类型变量都必须出现 在S(tvsleft)中,S是每个类型变量的替换映射 在类声明中实例中的相应类型 宣言。
这是过于技术性的,但基本上它表示右侧的类型变量集合 - 在本例中为{q}
,必须是左侧变量集的子集 - 这里{y, g, ys}
。显然情况并非如此。
您会注意到覆盖条件没有说明实例的上下文。这是你问题的根源!在确定功能依赖性是否成立时,上下文是 not 。我想你会同意我的意见,它明显失败了实例instance TApplies (y -> g) (y ': ys) q where ...
,这是编译器看到的。
解决方案很简单:将{-# LANGUAGE UndecidableInstances #-}
添加到文件顶部。除其他外,覆盖条件的目的是确保实例解析终止。如果您启用UndecidableInstances
,则表示“相信我,它会终止”。
作为旁注,目前的配方使用起来不是很好。例如,tApply (+) (1 :. 2 :. Nil)
会产生“模糊类型”错误,或类似的错误。更糟糕的是,tApply (+) (1 :. "s" :. Nil)
会产生完全相同的“模糊类型”错误!对于尝试使用此类编写代码的任何人都没有帮助。您必须在任何地方指定单形类型签名以使其按原样工作。
解决方案是将实例声明发生在以下情况:
instance (a ~ a') => TApplies a '[] a' where
instance (TApplies g ys q, y ~ y') => TApplies (y -> g) (y' ': ys) q where
然后,第一个案例按原样编译,键入默认踢入,并打印“3”。在第二种情况下,您会得到No instance for (Num [Char])
,这实际上很有帮助。
这样做的原因是,实例解析只关心实例头,而不关心上下文。 instance TApplies (y -> g) (y' ': ys) q
是编译器看到的内容,显然y
和y'
可以是任何不相关的类型。只有在打印值时,编译器才需要解析y ~ y'
约束,此时只需要表达式(+) 1 2
。
您可以实现没有数据类型的类型,如下所示:
{-# LANGUAGE GADTs #-}
data Cons a b
data Nil
data TList xs where
Nil :: TList Nil
(:.) :: x -> TList xs -> TList (Cons x xs)
然而,没有真正的理由这样做,因为DataKinds无法破坏其他工作代码。