2012-07-29 53 views
6

我有枚举类型,既可枚举的和有界的所有值的效用函数相关联的列表:生成整数的与Enum类型

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

和涉及映射枚举类型为整数数据类型:

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

其中vals是表示所有可能枚举值的整数列表。举例来说,如果我有

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

然后vals[0,1,2]

我希望能够以编程方式创建这些属性,只是给出了一个将a映射到可枚举类型和名称的函数。事情是这样的:

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

这不进行类型检查,因为没有连接与类型签名b调用enumerate的方式。所以我想我能做到这一点:

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

我不想包括如Attribute型(主要是因为我想存储的属性列表与b可能不同值的参数b类型 - 这就是为什么test的类型为a -> Intvals的类型是[Int] )。

我该如何编写这段代码,使其符合我的要求?

回答

6

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

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

随着ScopedTypeVariablesforall显式绑定类型变量也定义可用的,从而:

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]类型的第二个参数,统一将确保第一个参数也有类型[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]) 
+0

作用域类型变量工作perfecly,谢谢! – 2012-07-29 16:11:55

+0

此外,这是我第一次见到'undefined'用于执行有用的任务。 – 2012-07-29 16:25:43

+0

@ChrisTaylor:你当然可以使用'const'的不同特化,比如'asTypeOf2 :: [b] - >(a - > b) - > [b]',然后你可以编写'enumerate' asTypeOf2 \'f',但这可能不值得。 – Vitus 2012-07-30 18:56:11