2011-03-22 58 views
15

有沒有辦法以編程方式獲取類型類的實例列表?在Haskell中獲取類型類中的實例列表

它讓我覺得編譯器必須知道這些信息才能輸入檢查和編譯代碼,所以有什麼辦法可以告訴編譯器:嘿,你知道那個類的那些實例,請列出它們的列表在這裏(作爲字符串或其他任何表示形式)。

+0

實際上,編譯器不知道定義了哪些實例。它唯一能做的就是檢查一個類型是否是一個類型類的實例。 – fuz 2011-03-22 20:14:10

回答

12

您可以使用Template Haskell爲給定的類型類生成範圍內的實例。

import Language.Haskell.TH 

-- get a list of instances 
getInstances :: Name -> Q [ClassInstance] 
getInstances typ = do 
    ClassI _ instances <- reify typ 
    return instances 

-- convert the list of instances into an Exp so they can be displayed in GHCi 
showInstances :: Name -> Q Exp 
showInstances typ = do 
    ins <- getInstances typ 
    return . LitE . stringL $ show ins 

在GHCI運行此:

*Main> $(showInstances ''Num) 
"[ClassInstance {ci_dfun = GHC.Num.$fNumInteger, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Integer.Type.Integer]},ClassInstance {ci_dfun = GHC.Num.$fNumInt, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Types.Int]},ClassInstance {ci_dfun = GHC.Float.$fNumFloat, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Types.Float]},ClassInstance {ci_dfun = GHC.Float.$fNumDouble, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Types.Double]}]" 

另一種有用的技術是示出用於使用GHCI給定類型的類在範圍內的所有實例。

Prelude> :info Num 
class (Eq a, Show a) => Num a where 
    (+) :: a -> a -> a 
    (*) :: a -> a -> a 
    (-) :: a -> a -> a 
    negate :: a -> a 
    abs :: a -> a 
    signum :: a -> a 
    fromInteger :: Integer -> a 
    -- Defined in GHC.Num 
instance Num Integer -- Defined in GHC.Num 
instance Num Int -- Defined in GHC.Num 
instance Num Float -- Defined in GHC.Float 
instance Num Double -- Defined in GHC.Float 

編輯:要知道最重要的是,編譯器僅在範圍內的任何給定模塊中知道類型的類(或在ghci的提示等)。因此,如果您在沒有導入的情況下調用showInstances TH函數,則只會從Prelude中獲取實例。如果你有其他模塊,例如Data.Word,那麼你也會看到所有這些實例。

+1

看來這隻適用於GHC 7和TH 2.5。試圖在TH 2.4.0.1中做到這一點似乎並沒有給我想要的東西。這是一個新功能嗎? – mentics 2011-03-23 19:04:45

+0

我正在運行GHC 7.6.3。這裏是對答案的更新:'[ClassInstance]'現在是'[InstanceDec]'。另外,我需要在GHCi中運行':set -XTemplateHaskell'來複制這個例子。 – apolune 2014-03-20 20:52:26

2

我想,這是不可能的。我向你解釋類型類的實現(對於GHC),你可以看到,編譯器不需要知道哪些類型是類型類的實例。它只需要知道,具體類型是否是實例。

類型類將被轉換爲數據類型。作爲一個例子,讓我們Eq

class Eq a where 
    (==),(/=) :: a -> a -> Bool 

的類型類將被轉換成一種詞典,包含其所有的功能:

data Eq a = Eq { 
    (==) :: a -> a -> Bool, 
    (/=) :: a -> a -> Bool 
    } 

每個類型類的約束然後再翻譯成包含一個額外的參數詞典:

elem :: Eq a => a -> [a] -> Bool 
elem _ [] = False 
elem a (x:xs) | x == a = True 
       | otherwise = elem a xs 

變爲:

elem :: Eq a -> a -> [a] -> Bool 
elem _ _ [] = False 
elem eq a (x:xs) | (==) eq x a = True 
       | otherwise = elem eq a xs 

重要的是,該字典將在運行時通過。想象一下,你的項目包含很多模塊。 GHC不必檢查所有模塊的實例,只需查找實例是否在任何地方定義。

但是,如果你有可用的來源,我想老式的grep實例就足夠了。

+1

不完全正確。編譯器只需要知道某個特定類型是否是一個實例,但是在任何時候編譯器都可以枚舉範圍內的所有實例並生成所需的列表。例如,如果您使用':info Num',GHCi會執行此操作。 – 2011-03-22 23:03:55

0

不可能自動爲現有類執行此操作。對於你自己的班級及其實例,你可以做到這一點。你需要通過Template Haskell(或者準引用)來聲明所有的東西,並且它會自動生成一些奇怪的數據結構來編碼已聲明的實例。定義奇怪的數據結構,並使模板Haskell做到這一點是留給任何人有用例的細節。

也許你可以添加一些模板哈斯克爾或其他魔法到您的生成包括所有源文件在運行時可用的文本(c.f.程序奎因)。那麼你的程序將「用grep本身」 ......

6

這會爲你實例聲明像

instance Eq a => Eq [a] where 
    [] == [] = True 
    (x:xs) == (y:ys) = x == y && xs == ys 
    _ == _ = False 

instance (Eq a,Eq b) => Eq (a,b) where 
    (a1,b1) == (a2,b2) = a1 == a2 && b1 == b2 

一起儘快碰到很多問題一個具體實例(例如instance Eq Bool)。

你會得到實例的無限列表Eq - Bool[Bool][[Bool]][[[Bool]]]等,(Bool,Bool)((Bool,Bool),Bool)(((Bool,Bool),Bool),Bool)諸如此類,以作爲([((Bool,[Bool]),Bool)],Bool)等等這些這樣的各種組合一起。目前還不清楚如何在String中表示這些內容;即使列表TypeRep將需要一些非常聰明的枚舉。

編譯器可以(嘗試)推斷一個類型是否是Eq對於任何給定類型的實例,但它並不在範圍內的所有實例聲明讀出,然後纔剛剛起步推導所有可能的情況下,因爲永遠不會完成!

重要的問題當然是,你需要什麼?