2013-04-25 95 views
6

假設我們有HList的定義如下:爲HList的項目共享約束

data HL spec where 
    HLNil :: HL() 
    HLCons :: h -> HL t -> HL (h, t) 

是否有可能以某種方式強制執行其項目共享的約束?

作爲一個例子,下面是我試圖限制的項目有Show情況下,這將失敗,並Couldn't match type `Char' with `Int'

class HLSpecEach spec item 
instance HLSpecEach() item 
instance (HLSpecEach t item, h ~ item) => HLSpecEach (h, t) item 

a :: (Show item, HLSpecEach spec item) => HL spec -> Int 
a = undefined 

b :: HL (Int, (Char,())) 
b = undefined 

c = a b 

回答

4

容易,如果你有約束的種類和類型的家庭做。首先,讓我說,我更喜歡使用DataKinds爲清楚起見

data HList ls where 
    HNil :: HList '[] 
    HCons :: x -> HList xs -> HList (x ': xs) 

type family ConstrainAll (c :: * -> Constraint) (ls :: [*]) :: Constraint 
type instance ConstrainAll c '[] =() 
type instance ConstrainAll c (x ': xs) = (c x, ConstrainAll c xs) 

showAll :: ConstrainAll Show xs => HList xs -> [String] 
showAll HNil = [] 
showAll (HCons x xs) = (show x) : showAll xs 

如果不使用新的擴展是可能的,但更噁心。一種選擇是定義所有東西的自定義類別

class ShowAll ls where 
    showAll :: HList ls -> [Show] 
instance ShowAll() where 
    showAll _ = [] 
instance (ShowAll xs, Show x) => ShowAll (x,xs) 
    showAll (HCons x xs) = (show x) : (showAll xs) 

我覺得這很醜陋。一個更聰明的方法是假的約束種

class Constrained tag aType where 
    isConstained :: tag aType 

data HListT tag ls where 
    HNilT :: HListT tag() 
    HConsT :: x -> tag x -> HListT tag xs -> HListT tag (x,xs) 

data Proxy (f :: * -> *) = Proxy 
class ConstainedAll tag ls where 
    tagThem :: Proxy tag -> HList ls -> HListT tag ls 
instance ConstainedAll tag() where 
    tagThem _ _ = HNilT 
instance (ConstainedAll tag xs, Constrained tag x) => ConstainedAll tag (x,xs) where 
    tagThem p (HCons x xs) = HConsT x isConstained (tagThem p xs) 

然後你就可以像

data Showable x where Showable :: Show x => Showable x 
instance Show x => Constrained Showable x where isConstained = Showable 

--inferred type showAll' :: HListT Showable xs -> [String] 
showAll' HNilT = [] 
showAll' (HConsT x Showable xs) = (show x) : showAll' xs 

--inferred type: showAll :: ConstainedAll Showable xs => HList xs -> [String] 
showAll xs = showAll' (tagThem (Proxy :: Proxy Showable) xs) 

example = showAll (HCons "hello" (HCons() HNil)) 

應(還沒有測試)工作與任何GHC使用與GADTs,MPTC,靈活的上下文/情況下,和親切簽名(你可以輕鬆擺脫最後一個)。

編輯:GHC 7.6+,你應該使用

type family ConstrainAll (c :: k -> Constraint) (ls :: [k]) :: Constraint 

k而不是*)並打開PolyKinds,但這不會與GHC 7.4實施PolyKinds的(因此單態代碼工作)。以同樣的方式,確定

data HList f ls where 
    HNil :: HList f '[] 
    HCons :: !(f x) -> !(HList f xs) -> HList f (x ': xs) 

可以讓你避免代碼重複,當你想要的東西就像一個懶惰VS嚴格HLists或當你想詞典列​​表,或更高kinded類型的普遍變形等

+1

你可能想要補充一點,'Constraint'是constraints包的一部分,你必須爲'DataKinds'解決方案導入'Data.Constraint'。你還需要'ConstraintKinds'擴展。 :-) – 2013-04-25 09:37:36

+0

簡單...是的,它不像它可能比連接字符串或其他東西更難,對嗎?這很容易。簡單就是它的名字!當然!我的意思是,你看着它,你完全明白爲什麼。小菜一碟! )雖然真的,謝謝! – 2013-04-25 09:58:25

+1

公平地說,'Constraint'解決方案非常簡單(與其他所有方法相比,由於DataKinds非常有用)。您可以在此答案中看到更一般的方法:http://stackoverflow.com/a/ 12995367/11797整個線程實際上很有趣,我認爲它可以幫助你。 – 2013-04-25 11:12:14