代码之家  ›  专栏  ›  技术社区  ›  Nikita Volkov

HList项的共享约束

  •  6
  • Nikita Volkov  · 技术社区  · 13 年前

    假设我们对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
    
    1 回复  |  直到 13 年前
        1
  •  4
  •   Philip JF    13 年前

    如果您有约束种类和类型族,则很容易执行。首先,让我说我更喜欢使用 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))
    

    它应该(尚未测试)与任何具有GADT、MPTC、Flexible Contexts/Instances和Kind Signature的GHC一起工作(您可以很容易地摆脱最后一个)。

    编辑:在GHC 7.6+中,您应该使用

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

    ( k 而不是 * )并打开PolyKinds,但这不适用于PolyKinds的GHC 7.4实现(因此是单态代码)。同样,定义

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

    当您想要懒惰与严格的HLists之类的东西时,或者当您想要字典列表或更高级类型的通用变体时,可以避免代码重复。

    推荐文章