类型族和不连贯实例之间的奇怪交互

Strange interaction between type families and incoherent instances

此代码示例无法编译。

{-# LANGUAGE TypeFamilies, FlexibleInstances, UndecidableInstances, ScopedTypeVariables #-}
module IncoherentBug where

type family F a where
    F () = Int
    F a = a

class C a where
    c :: a -> Int

instance C Int where
    c y = y

instance {-# INCOHERENT #-} Monoid a => C a where
    c _ = 0

class TwoPossible a where
    x :: a

instance a ~ () => TwoPossible [a] where
    x = []

instance TwoPossible Bool where
    x = False

f :: (F a -> Int) -> [a] -> ()
f _ _ = ()


test = f (\v -> c v) x

基本上这里发生的是 f 的签名请求 x 的类型被解析为 [()],然后 v 的类型是 F ()Int,最后应选择 C 的第一个实例。相反,我得到了一个丢失的 Monoid Int 实例错误。

当我将 INCOHERENT 实例更改为 OVERLAPPABLE 时,代码编译正常。如果我用 IntF () 注释 v,它也有效。如果我用 [()].

注释 x (作为 f 的参数),它也会起作用

这是一个错误还是我误解了什么? ghc-mod 为 v 报告类型 F () 即使我没有这样注释它。除了错误消息提到 Int 之外,这意味着类型检查器为 v 找出了正确的类型,但由于某种原因没有选择更具体的实例。

我可能还应该注意,我使用的是 GHC 8。我不知道这个问题是否出现在早期版本中。

GHC拒绝这段代码是完全正确的。您有一个 C (F a) 约束,它来自

f c :: C (F a) => [a] -> ()

当您打开 INCOHERENT 时,GHC 会立即将其减少为

f c :: Monoid (F a) => [a] -> ()

甚至不考虑参数的类型。这就是不连贯的意思——一个实例化可以提供一个更具体的实例,但一个不连贯的实例无论如何都会匹配。当然,实例 ... => C a 匹配 每个 类型,因此如果您的 C 约束出现在任何地方,该实例将立即匹配。

使用OVERLAPPABLE等,C (F a)约束不能通过选择Monoid a => C a实例来减少,因为C Int 实例也可以匹配(这是连贯性,与不连贯性相反)。

如果你想看自己,向GHC询问f cINCOHERENTOVERLAPPABLE的推断类型。