6

我有一个实用函数,它枚举一个类型的所有值,该类型既可枚举又是有界的:

enumerate :: (Enum a, Bounded a) => [a]
enumerate = [minBound .. maxBound]

以及涉及将可枚举类型映射到整数的数据类型:

data Attribute a = Attribute { test :: a -> Int
                             , vals :: [Int]
                             , name :: String }

wherevals是表示所有可能的可枚举值的整数列表。例如,如果我有

data Foo = Zero | One | Two deriving (Enum,Bounded)

那么vals将是[0,1,2]

我希望能够以编程方式创建这些属性,只需给定一个将 an 映射a到可枚举类型的函数和一个名称。像这样的东西:

attribute :: (Enum b, Bounded b) => (a -> b) -> String -> Attribute a
attribute f str = Attribute (fromEnum . f) vs str
  where
    vs = map fromEnum enumerate

这不会进行类型检查,因为无法将调用enumerateb类型签名中的 连接起来。所以我想我可以这样做:

vs = map fromEnum $ enumerate :: [b]

但这也不能编译 - 编译器将其重命名bb1. 我试图变得更聪明,使用 GADTs 扩展:

attribute :: (Enum b, Bounded b, b ~ c) => {- ... -}
vs = map fromEnum $ enumerate :: (Enum c,Bounded c) => [c]

但同样,c被重命名为c1.

我不想将类型b作为参数包含在Attribute类型中(主要是因为我想存储具有可能不同值的属性列表b- 这就是为什么testhas typea -> Intvalshas type [Int])。

我怎样才能编写这段代码,以便它完成我想要它做的事情?

4

1 回答 1

6

类型变量的问题在于它们只绑定在类型签名中。在定义中任何类型变量的使用都将引用新的、新鲜的类型变量(即使它与类型签名中的名称完全相同)。

有两种方法可以从签名中引用类型变量:ScopedTypeVariables扩展名和asTypeOf.

在定义中也可以使用ScopedTypeVariables显式绑定的类型变量,因此:forall

attribute :: forall a b. (Enum b, Bounded b) =>
             (a -> b) -> String -> Attribute a
attribute f str = Attribute (fromEnum . f) vs str
  where
    vs = map fromEnum (enumerate :: [b])

另一种方式涉及asTypeOf定义为的函数:

asTypeOf :: a -> a -> a
asTypeOf = const

如果我们可以在第二个参数中得到一个类型的表达式[b],统一将确保第一个参数也有 type [b]。因为我们有f :: a -> bf undefined :: b,所以我们可以写:

attribute :: (Enum b, Bounded b) => (a -> b) -> String -> Attribute a
attribute f str = Attribute (fromEnum . f) vs str
  where
    vs = map fromEnum (enumerate `asTypeOf` [f undefined])
于 2012-07-29T16:01:07.437 回答