Haskell 类型族,理解错误信息

5

尝试使用Data.Has时,我编写了以下代码:

data Name = Name; type instance TypeOf Name = Text
type NameRecord = FieldOf Name;

我发现:

instance I NameRecord where
  ...

抛出编译错误,即:
非法类型同义词族在实例中应用
而:
instance (NameRecord ~ a) => I a where
  ...

编译正常。

我认为错误与GHC中标记为无效的票有关。

对于该票的回复如下:

I am not sure what you are suggesting. We cannot automatically transform

instance C (Fam Int) -- (1)

into

instance (Fam Int ~ famint) => C famint -- (2)

This works if there is only one instance, but as soon as there are two such instances, they always overlap.

Maybe you are suggesting that we should do it anyway and programmers should just take the implicit transformation into account. I don't think that this is a good idea. It's confusing for very little benefit (as you can always write the transformed instance yourself with little effort).

有人可以详细解释一下这个说明吗?最好提供一些示例代码,展示(1)失败而(2)不失败的情况,并说明原因。

1个回答

4
一个情况,其中(1)失败但(2)没有失败是微不足道的; 因为类型同义词(type ExampleOfATypeSynonym = ...)不允许在实例声明中,但它们允许在约束中,任何只有一个这样的实例的情况都是如此:
-- (1)
class Foo a
type Bla = ()
instance Foo Bla

...可以转换为:

-- (2)
class Foo a
type Bla = ()
instance (a ~ Bla) => Foo a

唯一失败的原因是(1)中不允许在实例声明中使用类型同义词,这是因为类型同义词就像类型函数一样:它们提供了从类型名称到类型名称的单向映射,因此如果您有一个type B = A和一个instance Foo B,那么不明显的是会创建一个Foo A的实例。规则存在的原因是为了使您必须编写instance Foo A以明确表明实际获得实例的类型。
在这种情况下,使用类型族是无关紧要的,因为问题实际上是您正在使用类型同义词NameRecord。您还必须记住,如果删除类型同义词并直接替换为FieldOf Name,编译仍将失败;这是因为“类型族”只是类型同义词的增强版本,因此在这种情况下,FieldOf Name也是Name :> Text的“类型同义词”。您必须使用数据族和数据实例来获取“双向”关联。

可以在GHC文档中找到有关数据族的更多信息。


我想你的意思是“...在(2)失败但(1)没有...”
让我们假设我们有一个像这样的类型类:
class Foo a where
  foo :: a

现在,您可以这样编写实例:
 instance Foo Int where
   foo = 0

 instance Foo Float where
   foo = 0

 main :: IO ()
 main = print (foo :: Float)

这段代码的运行结果与预期相符。然而,如果您将代码转换为以下形式:
{-# LANGUAGE FlexibleInstances, TypeFamilies #-}
class Foo a where
  foo :: a

instance (a ~ Int) => Foo a where
  foo = 0

instance (a ~ Float) => Foo a where
  foo = 0

main :: IO ()
main = print (foo :: Float)

它无法编译;它显示了错误:

test.hs:5:10:
    Duplicate instance declarations:
      instance a ~ Int => Foo a -- Defined at test.hs:5:10-27
      instance a ~ Float => Foo a -- Defined at test.hs:8:10-29

所以,这是你希望找到的示例。只有当有多个使用此技巧的Foo实例时才会发生这种情况。为什么呢?
当GHC解析类型类时,它只查看实例声明头部;即忽略=>之前的所有内容。当它选择一个实例时,它就会“承诺”它,并检查=>之前的约束条件是否为真。因此,一开始它看到了两个实例:
instance Foo a where ...
instance Foo a where ...

仅凭这些信息是显然无法确定使用哪个实例的。


你是否了解 GHC 解决类型类的方式是显式设计决策吗?如果是,原因是什么?(或者也许另一种方法实现起来太复杂了?) - huon
3
这是因为 Haskell 报告要求其表现出这种行为。而这背后的原因是,如果不这样做,就必须有一个算法来提供“类型与约束之间匹配程度”的启发式;你需要争辩:“是的,这个类型适合这个类实例,但那另一个实例更适合,因为{约束更少、缩减距离更短等}。”也许可以开发出这样的启发式算法,但这将违反“开放世界假设”,而该假设在涉及类型类时是一个关键概念。 - dflemstr
想象一下 instance String ~ a => Foo ainstance a ~ [b] => Foo a。这是需要算法来解决 Foo [Char] 的实例的一个例子。 - dflemstr
啊,有趣,谢谢!所以基本上原因是:它违反了OWA,因此破坏了类型类的一些好属性。(稍微离题一点,CSS有各种规则来明确地确定哪些选择器“更重要”并覆盖其他选择器,因此这样的启发式方法是可行的(并且可用)。) - huon
@dflemstr:感谢您的回复,非常有帮助。现在我意识到也许我错过了我的问题的重点,既然您已经让我更好地理解了实例是如何被检查的,特别是冲突。我在这里提出了一个新的相关问题:http://stackoverflow.com/questions/10491521/haskell-instance-definitions-for-type-families - Clinton
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接