HList项的共享约束

Nik*_*kov 6 haskell hlist

假设我们有一个HList的以下定义:

data HL spec where
  HLNil :: HL ()
  HLCons :: h -> HL t -> HL (h, t)
Run Code Online (Sandbox Code Playgroud)

是否有可能以某种方式对其项目强制执行共享约束?

例如,以下是我尝试将项目限制为具有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
Run Code Online (Sandbox Code Playgroud)

Phi*_* JF 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
Run Code Online (Sandbox Code Playgroud)

如果您不使用新的扩展,这是可能的,但更难看。一种选择是为所有内容定义自定义类

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)
Run Code Online (Sandbox Code Playgroud)

我觉得很难看。更聪明的方法是伪造约束类型

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)
Run Code Online (Sandbox Code Playgroud)

然后你可以使用它

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))
Run Code Online (Sandbox Code Playgroud)

它应该(尚未测试)与任何具有 GADT、MPTC、灵活上下文/实例和种类签名的 GHC 一起使用(您可以轻松摆脱最后一个)。

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

type family ConstrainAll (c :: k -> Constraint) (ls :: [k]) :: Constraint
Run Code Online (Sandbox Code Playgroud)

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

data HList f ls where
  HNil :: HList f '[]
  HCons :: !(f x) -> !(HList f xs) -> HList f (x ': xs)
Run Code Online (Sandbox Code Playgroud)

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