特定情况下的功能依赖性

时间:2014-11-17 19:42:03

标签: haskell functional-dependencies

作为简介,请参阅this questionmy 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’

最后两行对我来说似乎不对,虽然我希望我只是误解了一些东西。我的推理如下:

  1. 如果我们有a -> fa,那么我们有f
  2. 如果我们有TList h t,那么我们会ht
  3. 由于我们有TApplies f h t r,我们有fundep f h t -> r
  4. 因此,由于我们有fht,我们有r
  5. 这对我来说意味着功能依赖 是满足的。有人可以指出我的逻辑中的缺陷,也可能建议更合适的功能依赖选择? (请注意,GHC允许使用类似f r -> h的内容,但似乎没有为类型指示提供太多余地。)

1 个答案:

答案 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是编译器看到的内容,显然yy'可以是任何不相关的类型。只有在打印值时,编译器才需要解析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无法破坏其他工作代码。