在Haskell中约束类型级列表的元素

时间:2015-09-05 22:46:44

标签: haskell

我正在尝试编写一个类型系列,我可以使用它来约束类型级别列表的元素。我有这段代码:

{-# LANGUAGE PolyKinds, DataKinds, TypeOperators, TypeFamilies #-}

import GHC.TypeLits (KnownSymbol, symbolVal)
import GHC.Exts (Constraint)
import Data.Proxy (Proxy(..))

type family AllHave (c :: k -> Constraint) (xs :: [k]) :: Constraint
type instance AllHave c '[] = ()
type instance AllHave c (x ': xs) = (c x, AllHave c xs)

type family Head (xs :: [k]) :: k where
    Head (x ': xs) = x

headProxy :: proxy xs -> Proxy (Head xs)
headProxy _ = Proxy

test :: AllHave KnownSymbol xs => proxy xs -> String
test p = symbolVal (headProxy p)

main :: IO ()
main = putStrLn $ test (Proxy :: Proxy '["a", "b"])

根据我的理解,这应该有效,但是当我编译ghc spits out this:

Test.hs:18:10:
    Could not deduce (KnownSymbol (Head xs))
      arising from a use of ‘symbolVal’
    from the context (AllHave KnownSymbol xs)
      bound by the type signature for
                 test :: AllHave KnownSymbol xs => proxy xs -> String
      at Test.hs:17:9-52
    In the expression: symbolVal (headProxy p)
    In an equation for ‘test’: test p = symbolVal (headProxy p)

2 个答案:

答案 0 :(得分:4)

这里的问题是Headxs中被test传送时不会减少,因此Haskell无法从KnownSymbol (Head xs)中推断出AllHave KnownSymbol xs。它不应该:xs为空时会发生什么?

要解决此问题,您可以明确指出xs不是空的,如下所示:

test :: AllHave KnownSymbol (x ': xs) => proxy (x ': xs) -> String

答案 1 :(得分:2)

我对类型系列知之甚少,所以我会指出gallais's answer来解释代码中出了什么问题。这是一种非常不同的方法,具有许多演示功能。可能有更好的方法;我不知道。

data CList :: (k -> Constraint) -> [k] -> * where
  CNil :: CList c '[]
  CCons :: c t => proxy t -> CList c ts -> CList c (t ': ts)

mapCSimple :: (forall a . c a => Proxy a -> b) -> CList c as -> [b]
mapCSimple f CNil = []
mapCSimple f (CCons (t :: proxy t) ts) = f (Proxy :: Proxy t) : mapCSimple f ts

toStrings :: CList KnownSymbol v -> [String]
toStrings = mapCSimple symbolVal

class KnownSymbols (xs :: [Symbol]) where
  known :: proxy xs -> CList KnownSymbol xs

instance KnownSymbols '[] where
  known _ = CNil

instance (KnownSymbol x, KnownSymbols xs) => KnownSymbols (x ': xs) where
  known _ = CCons Proxy $ known Proxy

exampleG :: KnownSymbols xs => proxy xs -> String
exampleG p = show . toStrings $ known p

这给出了

> putStrLn $ exampleG (Proxy :: Proxy '["Hello", "Darkness"])
["Hello","Darkness"]

为了得到更像你想要的东西,

cHead :: CList c (a ': as) -> Dict (c a)
cHead (CCons prox _) = Dict

test :: forall x xs . CList KnownSymbol (x ': xs) -> String
test xs = case cHead xs of Dict -> symbolVal (Proxy :: Proxy x)

test2 :: (KnownSymbols xs, xs ~ (y ': ys)) => proxy xs -> String
test2 prox = test (known prox)

这是

> putStrLn $ test2 (Proxy :: Proxy '["Hello", "Darkness"])
Hello

这是另一个有趣的功能:

data HList :: (k -> *) -> [k] -> * where
  HNil :: HList f '[]
  HCons :: f a -> HList f as -> HList f (a ': as)

mapC :: (forall a . c a => Proxy a -> f a) -> CList c as -> HList f as
mapC f CNil = HNil
mapC f (CCons (t :: proxy t) ts) = HCons (f (Proxy :: Proxy t)) (mapC f ts)