2012-07-29 55 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