枚举类型类中的关联类型

时间:2015-12-05 21:12:16

标签: haskell typeclass

给定类似于以下的类型类定义,我想枚举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类型变量与函数a约束中的enumMyClassId相同。一种可能的解决方法是将函数enumMyClassId更改为以下内容:

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

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

1 个答案:

答案 0 :(得分:3)

您的功能被拒绝的原因是任何使用的尝试都会导致歧义。在使用网站上,您可以提供约束MyClassId a的签名,但不能指定a。您应该能够通过暂时启用AllowAmbiguousTypes将错误消息推迟到使用网站(更容易理解)。

有两个惯用修复:

使用代理

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

您通常会从Proxy传递Data.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#,这是一个完全假的值。