类型系列的难以理解的错误消息

时间:2013-06-04 20:08:48

标签: haskell type-families

我正在努力了解类型系列而没有太大的成功。这是一个最小的例子:

{-# LANGUAGE TypeFamilies #-}

class Object obj where
  type Unit obj :: *
  unit :: Unit obj

instance (Object obj, Object obj') => Object (obj, obj') where
  type Unit (obj, obj') = (Unit obj, Unit obj')
  unit = (unit, unit)

我认为目的是相当透明的(尝试定义产品类别)。

这给了我:

objs.hs:10:10:
    Could not deduce (Unit obj' ~ Unit obj1)
    from the context (Object obj, Object obj')
      bound by the instance declaration at objs.hs:8:10-56
    NB: `Unit' is a type function, and may not be injective
    The type variable `obj1' is ambiguous
    Possible fix: add a type signature that fixes these type variable(s)
    Expected type: Unit (obj, obj')
      Actual type: (Unit obj0, Unit obj1)
    In the expression: (unit, unit)
    In an equation for `unit': unit = (unit, unit)
    In the instance declaration for `Object (obj, obj')'

我尝试添加类型签名:

unit = (unit :: Unit obj, unit :: Unit obj') 

但这只会让事情变得更糟。

以下修改编译:

{-# LANGUAGE TypeFamilies #-}

class Object obj where
  type Unit obj :: *
  unit :: obj -> Unit obj

instance (Object obj, Object obj') => Object (obj, obj') where
  type Unit (obj, obj') = (Unit obj, Unit obj')
  unit (o, o') = (unit o, unit o')

但我不喜欢unit的多余论点。

是否可以定义无参数unit

2 个答案:

答案 0 :(得分:21)

你正在尝试做的事情对GHC来说很棘手,因为GHC在错误信息中说,类型系列确实不需要是单射的。

注入意味着什么?

如果F暗示F x ~ F y,则类型函数x ~ y称为单射函数。如果F是通过data定义的普通类型构造函数,那么这总是正确的。但是,对于类型系列,它不成立。

例如,根据您对Object的定义,定义以下实例没有问题:

instance Object Int where
  type Unit Int = Int
  unit = 0

instance Object Char where
  type Unit Char = Int
  unit = 1

现在,如果你写unit :: Int,那么GHC怎么可能确定它应该评估为0还是1?甚至不写unit :: Unit Int使得它更加清晰,因为

Unit Int ~ Int ~ Unit Char

所以这三种类型都应该是可以互换的。由于Unit不能保证是唯一的,因此根本无法从Unit x x的知识中得出唯一的结论......

结果是unit可以定义,但不能使用。

解决方案1:虚拟或代理参数

您已经列出了解决此问题的最常用方法。通过将类型签名更改为

,添加一个帮助GHC实际确定相关类型参数的参数
unit :: obj -> Unit obj

unit :: Proxy obj -> Unit obj

适用于Proxy的定义,例如

data Proxy a

解决方案2:手动证明可逆性

一个可能鲜为人知的选择是你可以向GHC证明你的类型函数是可逆的。

这样做的方法是定义一个反型家庭

type family UnUnit obj :: *

并使可逆性成为类型类的超类约束:

class (UnUnit (Unit obj) ~ obj) => Object obj where
  type Unit obj :: *
  unit :: Unit obj

现在你必须做额外的工作。对于类的每个实例,您必须定义 Unit的实际倒数正确。例如,

instance (Object obj, Object obj') => Object (obj, obj') where
  type Unit (obj, obj') = (Unit obj, Unit obj')
  unit = (unit, unit)

type instance UnUnit (obj, obj') = (UnUnit obj, UnUnit obj')

但鉴于此修改,定义类型检查。现在,如果GHC在某个特定类型unit遇到T并且想要确定S类型Unit S ~ T,那么它可以应用超类约束来推断< / p>

S ~ UnUnit (Unit S) ~ UnUnit T

如果我们现在尝试为Object IntObject Char定义错误的实例,这两个实例都将Unit IntUnit Char都映射为Int,那是行不通的,因为我们必须决定UnObject Int应该是Int还是Char,但是不能同时...... {/ p>

答案 1 :(得分:3)

由于关联类型不是单射的(在下面定义),因此需要一个参数才能键入check。例如,请考虑以下(不正确的)代码。

class Valuable o where
  type Value o :: *
  value :: Value o

data Pearl
data Diamond

instance Valuable Pearl where
  type Value Pearl = Int
  value = 1000

instance Valuable Diamond where
  type Value Diamond = Int
  value = 10000

请注意,Value a ~ Value b并不意味着a ~ b因为注入会产生它。因此,现在value的价值是多么模糊。如果我们从value :: Int开始限制Value Pearl ~ Value Diamond ~ Int的类型,它甚至无济于事。


也就是说,代码中存在一些非常好的并行性。

import Control.Arrow

class Object obj where
  type Unit obj :: *
  unit :: obj -> Unit obj

instance (Object obj, Object obj') => Object (obj, obj') where
  type Unit (obj, obj') = (Unit obj, Unit obj')
  unit = unit *** unit