2013-04-25 49 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