为什么Haskell说这是模棱两可的?

时间:2017-12-18 21:28:40

标签: class haskell types ambiguous

我有一个像这样定义的类型类:

class Repo e ne | ne -> e, e -> ne where
  eTable :: Table (Relation e)

当我尝试编译它时,我得到了这个:

* Couldn't match type `Database.Selda.Generic.Rel
                             (GHC.Generics.Rep e0)'
                     with `Database.Selda.Generic.Rel (GHC.Generics.Rep e)'
      Expected type: Table (Relation e)
        Actual type: Table (Relation e0)
      NB: `Database.Selda.Generic.Rel' is a type function, and may not be injective
      The type variable `e0' is ambiguous
    * In the ambiguity check for `eTable'
      To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
      When checking the class method:
        eTable :: forall e ne. Repo e ne => Table (Relation e)
      In the class declaration for `Repo'
   |
41 |   eTable :: Table (Relation e)
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

由于我明确声明e确定ne,反之亦然,我期待一切都明确无误。

但是,如果我尝试像测试目的那样定义我的类,它会编译:

data Test a = Test a
class Repo e ne | ne -> e, e -> ne where
    eTable :: Maybe (Test e)

我不太确定导致这种情况的TableRelation类型的交易是什么。

2 个答案:

答案 0 :(得分:4)

Test是单射的,因为它是一个类型构造函数。

Relation不是单射的,因为它是一个类型族。

因此含糊不清。

愚蠢的例子:

type instance Relation Bool = ()
type instance Relation String = ()

instance Repo Bool Ne where
   eTable :: Table ()
   eTable = someEtable1

instance Repo String Ne where
   eTable :: Table ()
   eTable = someEtable2

现在,eTable :: Table ()是什么?它可以是来自第一个或第二个实例的那个。这是不明确的,因为Relation不是单射的。

答案 1 :(得分:3)

歧义的来源实际上与ne没有在类中使用(你通过使用函数依赖关系)而无关。

错误消息的关键部分是:

  Expected type: Table (Relation e)
    Actual type: Table (Relation e0)
  NB: `Database.Selda.Generic.Rel' is a type function, and may not be injective

请注意,e无法匹配,而且NB消息会引起您对类型功能和注入性问题的关注(您真的必须知道这是什么消息有用的所有方法,但它具有查看所需内容所需的所有术语,因此编程错误消息会很好。

它所抱怨的问题是类型构造函数和类型族之间的关键区别。类型构造函数总是单射的,而一般的类型函数(特别是类型族)不一定是。

在没有扩展名的标准Haskell中,构建复合类型表达式的唯一方法是使用类型构造函数,例如Test中的左侧data Test a = Test a 。我可以将Test(善意* -> *)应用于类似Int(类型为*)的类型,以获取Test Int类型{{1} }})。类型构造函数为injective,这意味着对于任何两种不同类型*ab是来自Test a 1 。这意味着在进行类型检查时,您可以“向后运行”#34 ;;如果我有Test bt1两种类型,这两种类型都是应用t2的结果,我知道Testt1是假设的为了平等,那么我可以"取消应用" t2获取参数类型并检查它们是否相等(或推断它们中的哪一个是否是我还没有想到的,另一个是已知的,或等等)。

类型系列(或任何其他形式的类型函数,不知道是单射的)不能为我们提供保证。如果我有两种类型Testt1应该是相同的,并且它们都是应用某些t2的结果,那么就没有办法去从结果类型到TypeFamily应用的类型。特别是,由于TypeFamilyTypeFamily a相等,TypeFamily ba相等,所以无法得出结论;类型系列可能恰好将两个不同类型ba映射到相同的结果(注入的定义是它不会这样做)。因此,如果我知道哪种类型b但不知道a,那么知道bTypeFamily a相等并不能再向我提供任何信息关于TypeFamily b应该是什么类型。

不幸的是,由于标准Haskell只有类型构造函数,Haskell程序员得到了良好的训练,只是假设类型检查器可以通过复合类型向后工作以连接组件。我们通常不会注意到类型检查器需要向后工作,我们习惯于仅仅查看具有相似结构的类型表达式并跳过明显的结论而不通过类型检查器必须经历的所有步骤。但是因为类型检查是基于计算每个表达式的类型,包括自下而上 2 和自上而下 3 并确认它们是一致的,所以键入检查表达式的类型涉及类型家庭很容易遇到模糊问题,它看起来很明显"对我们人类毫不含糊。

b示例中,请考虑类型检查器如何处理您使用Repo的位置,eTable表示(Int, Bool)。自上而下的视图会看到它在需要某些e的上下文中使用。它会计算Table (Relation (Int, Bool))评估的内容:说出Relation (Int, Bool),因此我们需要Set Int。对于任何Table (Set Int),自下而上的传递只是eTable可以是Table (Relation e)

我们使用标准Haskell的所有经验都告诉我们,这很明显,我们只是将e实例化为e(Int, Bool)再次评估为Relation (Int, Bool),我们就是这样做的。重做。但事实并非如此。由于Set Int不是单射的,Relation可能会有其他选择,eSet Int提供Relation e:也许Int。但是,如果我们选择e(Int, Bool)Int,我们需要查找两个不同的Repo个实例,这些实例将具有不同的实现 { {1}},即使他们的类型相同。

每次使用eTable eTable时,即使添加类型注释也无济于事。类型注释仅向自上而下的视图添加额外信息,这是我们通常已经拥有的。类型检查器仍然存在这样的问题:eTable :: Table (Relation (Int, Bool))可能存在(不管是否存在)e的其他选项,导致(Int, Bool)匹配该类型注释,因此它不知道要使用哪个实例。 任何可能使用eTable都会出现此问题,因此当您定义该类时会将其报告为错误。它基本上是出于同样的原因,当你有一个类的成员时,如果你的类型没有使用类头中的所有类型变量,你会遇到问题。你必须考虑"仅在类型系列下使用的变量"和"变量一样,并没有全部使用"。

您可以通过向eTable添加Proxy参数来解决此问题,以便修复类型检查器可以的类型变量eTable >"倒退"。所以e

或者,使用eTable :: Proxy e -> Table (Relation e)扩展程序,您现在可以按照错误消息的建议执行操作,并启用TypeApplications以使类接受,然后使用{{{ 1}} 告诉编译器你想要AllowAmbiguousTypes的选择。类型注释eTable @(Int, Bool)不起作用的原因是类型注释是在类型检查器自上而下查看时添加到上下文的额外信息,但类型应用程序在类型时添加额外信息检查员正在自下而上。而不是"该表达式需要具有与此类型统一的类型"它"这种多态函数 在这种类型下实例化"。

1 类型构造函数实际上甚至比注入性更受限制;将e应用于任何类型eTable :: Table (Relation (Int, Bool))会产生具有已知结构Test的类型,因此整个Haskell类型的Universe会以a形式直接镜像。更一般的内射类型函数可以做更多的重新排列",例如将Test a映射到Test t,只要它没有将Int映射到Bool

2 从通过组合表达式

的子部分产生的类型

3 来自使用它的上下文所需的类型