枚举类型类中的关联类型

Enumerating an associated type in a typeclass

给定如下所示的类型类定义,我想为作为 MyClass.

实例的任何类型枚举 MyClassId a 类型
{-# LANGUAGE TypeFamilies     #-}
{-# LANGUAGE FlexibleContexts #-}

class
  ( Enum (MyClassId e)
  , Bounded (MyClassId e))
  => MyClass e where
    type MyClassId e :: *

enumMyClassId :: MyClass a => [MyClassId a]
enumMyClassId = enumFrom minBound

但是,当我尝试编译这段代码时,GHC 7.10.2 抱怨以下消息:

enumTypeClass.hs:12:18:
    Couldn't match type ‘MyClassId a0’ with ‘MyClassId a’
    NB: ‘MyClassId’ is a type function, and may not be injective
    The type variable ‘a0’ is ambiguous
    Expected type: [MyClassId a]
      Actual type: [MyClassId a0]
    In the ambiguity check for the type signature for ‘enumMyClassId’:
      enumMyClassId :: forall a. MyClass a => [MyClassId a]
    To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
    In the type signature for ‘enumMyClassId’:
      enumMyClassId :: MyClass a => [MyClassId a]

我不确定为什么它不能推断出 a 类型变量与函数 enumMyClassId 约束中的 a 相同。一种可能的修复方法是将函数 enumMyClassId 更改为以下内容:

enumMyClassId :: MyClass a => a -> [MyClassId a]
enumMyClassId _ = enumFrom minBound

但这不是很优雅,因为它引入了一个未使用的变量只是为了让程序进行类型检查。有没有不涉及上述技巧的解决方案?

您的函数被拒绝的原因是任何 使用 的尝试都会导致歧义。在使用现场,可以给出约束MyClassId a的签名,但不能指定a。您应该能够通过暂时启用 AllowAmbiguousTypes.

将错误消息推迟到使用站点(在那里更容易理解)

有两个惯用的修复:

使用代理

enumMyClassId :: MyClass a => proxy a -> [MyClassId a]

您通常会从 Data.Proxy 传递一个 Proxy,但您也可以使用类型最后一个参数为 a.

的任意值

使用标记类型

这种方法通常不太方便,但有时对于记忆内容很重要。

Edward Kmett 的 tagged 包裹给你

newtype Tagged s b = Tagged {unTagged :: b}

然后你会写

enumMyClassId :: MyClass a => Tagged a [MyClassId a]

并称之为

enumMyClassId :: Tagged a [MyClassId a]

还有一种 GHC 特殊方法,它甚至不打算移植,但它提供了类似于标记类型的性能和代理便利性。

魔术代理

可以使用GHC在编译时彻底抹杀的神奇Proxy# type。这就像第一个解决方案一样工作,但使用 Proxy# a 类型而不是 proxy a 类型。当调用这样的函数时,你传递了 proxy#,这是一个完全虚假的值。