首页 文章

枚举类型类中的关联类型

提问于
浏览
1

给定类似于以下的类型类定义,我想枚举 MyClassId a 类型作为 MyClass 实例的任何类型 .

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

但这并不是很优雅,因为它引入了一个未使用的变量,只是为了使程序检查 . 是否有一些解决方案不涉及上述技巧?

1 回答

  • 3

    您的函数被拒绝的原因是任何使用它的尝试都会导致歧义 . 在使用站点,您可以提供约束 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# ,这是一个完全假的值 .

相关问题