14

我试图理解类型家庭,但没有取得多大成功。这是一个最小的例子:

{-# 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

4

2 回答 2

21

您尝试做的事情对于 GHC 来说很棘手,因为正如 GHC 在错误消息中所说,类型族确实不需要是单射的。

内射是什么意思?

一个类型函数F称为单射 ifF 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 怎么可能确定它是否应该评估为0or 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并想要确定一个类型SUnit S ~ T它可以应用超类约束来推断

S ~ UnUnit (Unit S) ~ UnUnit T

如果我们现在尝试像上面那样定义坏实例 forObject IntObject Charwhich both mapUnit IntUnit Charto be Int,那是行不通的,因为我们必须决定是UnObject Intshould beInt还是Char,但不能同时拥有...

于 2013-06-04T21:16:27.660 回答
3

由于关联类型不是单射的(定义如下),因此您需要一个参数来进行类型检查。例如,考虑以下(不正确的)代码。

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如果我们限制since的类型,它甚至没有帮助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
于 2013-06-04T21:06:55.227 回答