为什么这个Haskell代码使用fundeps进行类型检查,但是对类型系列产生了不可触摸的错误?

时间:2017-12-10 00:53:59

标签: haskell functional-dependencies type-families

给出一些类型定义:

data A
data B (f :: * -> *)
data X (k :: *)

...和这个类型类:

class C k a | k -> a

......这些(为最小的例子而设计很多)函数定义类型检查:

f :: forall f. (forall k. (C k (B f)) => f k) -> A
f _ = undefined

g :: (forall k. (C k (B X)) => X k) -> A
g = f

但是,如果我们使用类型系列而不是具有功能依赖性的类:

type family F (k :: *)

...然后等效的函数定义无法进行类型检查:

f :: forall f. (forall k. (F k ~ B f) => f k) -> A
f _ = undefined

g :: (forall k. (F k ~ B X) => X k) -> A
g = f

• Couldn't match type ‘f0’ with ‘X’
    ‘f0’ is untouchable
      inside the constraints: F k ~ B f0
      bound by a type expected by the context:
                 F k ~ B f0 => f0 k
  Expected type: f0 k
    Actual type: X k
• In the expression: f
  In an equation for ‘g’: g = f

我阅读了the OutsideIn(X) paper的第5.2节,它描述了可触摸和不可触摸的类型变量,而我有点了解这里发生了什么。如果我向f添加一个额外的参数,将f的选择推到内部forall之外,那么程序可以检查:

f :: forall f a. f a -> (forall k. (F k ~ B f) => f k) -> A
f _ _ = undefined

g :: forall a. X a -> (forall k. (F k ~ B X) => X k) -> A
g = f

然而,在这个特定的例子中我特别困惑的是为什么函数依赖具有不同的行为。我听过人们在不同时间声称像这样的功能依赖等同于类型族和等式,但这表明实际上并非如此。

在这种情况下,函数依赖提供了哪些信息,允许f以类型族不实例化的方式实例化?

2 个答案:

答案 0 :(得分:0)

我不知道我是否应该将此作为答案发布,因为它仍然非常浪漫,但我确实认为这是本质上正在发生的事情:

要评估(C k (B X)) => X k值,您必须为k选择具体类型,并指向满足约束的instance C k (B X)。要做到这一点,你必须说出类型类'a参数的格式为B f,编译器可以从中提取f类型(并发现它是X在这种情况下) - 重要的是,在实际查看实例之前,它可以执行此操作,这将是f不可触及的点。

要评估(F k ~ B X) => X k,它会有所不同。在这里你不需要指向一个具体的实例,你只需要保证如果编译器查找 F k的类型,那么这个类型将是{ {1}}。但在实际查找实例之前,编译器无法推断出B X具有F k形式,因此也不会使用此来将B f与外部量化参数统一起来,因为不可触及。

因此,GHC的行为至少不是完全不合理的。我仍然不确定 是否应该以这种方式行事。

答案 1 :(得分:0)

好的,我有机会玩这个。有几个分心:

在“类型系列”版本中,只有f的定义才会显示错误'f0' is untouchable。 (您可以使用AllowAmbiguousTypes来抑制该问题;只是将错误推迟显示在g上。)请在此处忽略g

然后没有AllowAmbiguousTypes,f的错误消息提供了更多信息:

• Couldn't match type ‘f0’ with ‘f’
    ‘f0’ is untouchable
      inside the constraints: F k ~ B f0
      bound by the type signature for:
                 f :: F k ~ B f0 => f0 k
  ‘f’ is a rigid type variable bound by
    the type signature for:
      f :: forall (f :: * -> *). (forall k. F k ~ B f => f k) -> A
  Expected type: f0 k
    Actual type: f k

啊哈!一个rigid type variable问题。我想因为fk的等式约束修复,因为...... {/ p>

转到没有FunDep的{​​{1}}版本,我们可以将哪些类型称为g?尝试

f

拒绝消息(对于f (undefined undefined :: X a) -- OK f (undefined "string" :: X String) -- rejected f Nothing -- OK f (Just 'c') -- rejected 示例)是

X String

请注意,该消息大约是• Couldn't match type ‘k’ with ‘String’ ‘k’ is a rigid type variable bound by a type expected by the context: forall k. C k (B X) => X k Expected type: X k Actual type: X String • In the first argument of ‘f’, namely ‘(undefined "string" :: X String)’ 不是 k,这是根据FunDep确定的 - 或者我们是否能找到合适的f

<强>解释

函数k的签名表示f存在量化/更高排名。然后,我们无法允许有关k的任何类型信息进入周围环境。我们无法为k提供任何(非底部)值,因为其类型会侵入k

这是一个更简单的例子:

forall

因此原始的f2 :: forall f. (forall k. f k) -> A f2 _ = undefined f2 Nothing -- OK f2 (Just 'c') -- rejected rigid type var 版本编译是一种分心:它无法居住。 (根据我之前的怀疑,这是FunDep的常见症状。)