我有两个异构的列表结构。第一个HList
是普通的异构列表,第二个Representation
是其中所有成员都已设置的异构列表。
{-# Language KindSignatures, DataKinds, TypeOperators, TypeFamilies, GADTs, FlexibleInstances, FlexibleContexts #-}
import Data.Kind
import Data.Set
data Representation (a :: [Type]) where
NewRep :: Representation '[]
AddAttribute :: (Ord a, Eq a) => Set a -> Representation b -> Representation (a ': b)
(%>) :: (Ord a, Eq a) => [a] -> Representation b -> Representation (a ': b)
(%>) = AddAttribute . fromList
infixr 6 %>
-- | A HList is a heterogenenous list.
data HList (a :: [Type]) where
HEmpty :: HList '[]
(:>) :: a -> HList b -> HList (a ': b)
infixr 6 :>
(如果有帮助,我在底部创建了Show
的这些实例。)
现在,我有许多功能可以在HList
上运行,但不能在Representation
上运行。我可以重写所有功能,但这是一个很大的痛苦。我希望是否有某种方法可以在Representation
s中制作HList
,然后再返回。这样,我可以使用所有相关功能而不必重新定义它们。所以我开始这样做。制作从Representation
到HList
s的函数非常容易:
type family Map (f :: Type -> Type) (xs :: [Type]) :: [Type] where
Map f '[] = '[]
Map f (a ': b) = f a ': Map f b
-- | soften takes an attribute representation and converts it to a heterogeneous list.
soften :: Representation a -> HList (Map Set a)
soften NewRep = HEmpty
soften (AddAttribute a b) = a :> soften b
但是,另一种方法要困难得多。我尝试了以下方法:
-- | rigify takes a heterogeneous list and converts it to a representation
rigify :: HList (Map Set a) -> Representation a
rigify HEmpty = NewRep
rigify (a :> b) = AddAttribute a $ rigify b
但是,如果失败,编译器将无法在第一行中推断出a ~ '[]
。并在第二次失败。
在我看来,编译器无法以与转发相同的方式进行向后推理。这并不是真的很令人惊讶,但是我不知道到底是什么问题,所以我不太确定如何正确地进行编译。我的想法是建立一个像Map
相反的类型族:
type family UnMap (f :: Type -> Type) (xs :: [Type]) :: [Type] where
UnMap f '[] = '[]
UnMap f ((f a) ': b) = a ': UnMap f b
,然后根据rigify
而不是UnMap
重写Map
:
-- | rigify takes a heterogeneous list and converts it to a representation
rigify :: HList a -> Representation (UnMap Set a)
rigify HEmpty = NewRep
rigify (a :> b) = AddAttribute a $ rigify b
这似乎可以减轻问题,但仍无法编译。这次我们遇到的问题是,第二行中的a
无法显示为Set x
所必需的类型AddAttribute
。这对我来说很有意义,但我不知道该如何解决。
如何从异构列表转换为Representation
?
Show
实例:
instance Show (HList '[]) where
show HEmpty = "HEmpty"
instance Show a => Show (HList '[a]) where
show (a :> HEmpty) = "(" ++ show a ++ " :> HEmpty)"
instance (Show a, Show (HList (b ': c))) => Show (HList (a ': b ': c)) where
show (a :> b) = "(" ++ show a ++ " :> " ++ tail (show b)
instance Show (Representation '[]) where
show NewRep = "NewRep"
instance Show a => Show (Representation '[a]) where
show (AddAttribute h NewRep) = '(' : show (toList h) ++ " %> NewRep)"
instance (Show a, Show (Representation (b ': c))) => Show (Representation (a ': b ': c)) where
show (AddAttribute h t) = '(' : show (toList h) ++ " %> " ++ tail (show t)
答案 0 :(得分:2)
HList
通常是错误的。我的意思是,一旦您尝试做很多事情,您很可能会遇到很多问题。您可以解决问题,但这很烦人并且效率低下。还有另一种非常相似的结构可以在跌倒之前走得更远。
data Rec :: [k] -> (k -> Type) -> Type where
Nil :: Rec '[] f
(:::) :: f x -> Rec xs f -> Rec (x ': xs) f
type f ~> g = forall x. f x -> g x
mapRec :: (f ~> g) -> Rec xs f -> Rec xs g
mapRec _ Nil = Nil
mapRec f (x ::: xs) = f x ::: mapRec f xs
请注意,您可以进行某种类型的映射,而无需引入任何类型族!
现在您可以定义
data OSet a = Ord a => OSet (Set a)
newtype Representation as = Representation (Rec as OSet)
很多通用HList
函数可以很容易地重写以支持Rec
。
如果愿意,您可以编写双向模式同义词来模拟当前界面。
答案 1 :(得分:1)
Ord a
使Eq a
变得多余:Ord a
暗示Eq a
是因为class Eq a => Ord a
。
data Representation (a :: [Type]) where
...
AddAttribute :: Ord a => Set a -> Representation b -> Representation (a ': b)
(%>) :: Ord a => [a] -> Representation b -> Representation (a ': b)
您不能用这种类型的文字来写rigify
:soften
会丢弃存储在每个Ord
上的AddAttribute
-ness。您可以使用
data OSet a where OSet :: Ord a => Set a -> OSet a
soften :: Representation xs -> HList (Map OSet xs)
rigify :: HList (Map OSet xs) -> Representation xs
,您可以在该顶部应用古老的“成对列表就是一对列表”技巧
type family AllCon (xs :: [Constraint]) :: Constraint where
AllCon '[] = ()
AllCon (x : xs) = (x, AllCon xs)
data Dict c = c => Dict
soften :: Representation xs -> (HList (Map Set xs), Dict (AllCon (Map Ord xs)))
rigify :: AllCon (Map Ord xs) => HList (Map Set xs) -> Representation xs
尽管我会选择前者,因为它更简洁。
使用unsafeCoerce
。另一种方法是使用GADT验证某些类型信息并编写证明。尽管这是一个好习惯,但是这要求您拖动(可能大)表示简单 true 值的值,因此无论如何您最终都会使用unsafeCoerce
来避免它们。您可以跳过打样,直接转到最终产品。
-- note how I always wrap the unsafeCoerce with a type signature
-- this means that I reduce the chance of introducing something actually bogus
-- I use these functions instead of raw unsafeCoerce in rigify, because I trust
-- these to be correct more than I trust unsafeCoerce.
mapNil :: forall f xs. Map f xs :~: '[] -> xs :~: '[]
mapNil Refl = unsafeCoerce Refl
data IsCons xs where IsCons :: IsCons (x : xs)
mapCons :: forall f xs. IsCons (Map f xs) -> IsCons xs
mapCons IsCons = unsafeCoerce IsCons
rigify :: HList (Map OSet xs) -> Representation xs
rigify HEmpty = case mapNil @OSet @xs Refl of Refl -> NewRep
rigify (x :> xs) = case mapCons @OSet @xs IsCons of
IsCons -> case x of OSet x' -> AddAttribute x' (rigify xs)
适当的证明如下:
data Spine :: [k] -> Type where
SpineN :: Spine '[]
SpineC :: Spine xs -> Spine (x : xs)
mapNil' :: forall f xs. Spine xs -> Map f xs :~: '[] -> xs :~: '[]
mapNil' SpineN Refl = Refl
mapNil' (SpineC _) impossible = case impossible of {}
mapCons' :: forall f xs. Spine xs -> IsCons (Map f xs) -> IsCons xs
mapCons' SpineN impossible = case impossible of {}
mapCons' (SpineC _) IsCons = IsCons
对于每个列表xs
,Spine xs
(只有一个单例类型)只有一个(完全定义的)值。为了从真实的证明(如mapNil'
到方便的版本(如mapNil
),请删除所有单例参数,并确保返回类型仅仅是命题。 (一个简单的命题就是一个具有0或1个值的类型。)将其替换为可以深入评估其余参数并使用unsafeCoerce
作为返回值的主体。
答案 2 :(得分:1)
rigify
的期望行为可以通过使用多参数类型类来获取。
class Rigible (xs :: [Type]) (ys :: [Type]) | xs -> ys where
rigify :: HList xs -> Representation ys
instance Rigible '[] '[] where
rigify HEmpty = NewRep
instance (Ord h, Rigible t t') => Rigible (Set h ': t) (h ': t') where
rigify (a :> b) = AddAttribute a $ rigify b
在这里,我们使用具有附加函数Rigible
的多参数类型类rigify
。我们的两个参数是表示形式的类型和异构列表的类型。它们在功能上有所依赖,以避免产生歧义。
通过这种方式,只有HList
个完全由集合组成。您甚至还可以从这里在Rigible
中添加soften
的定义。
Rigible
这需要附加的编译指示
-- | soften takes a representation and converts it to a heterogeneous list.
-- | rigify takes a heterogeneous list and converts it to a representation.
class Rigible (xs :: [Type]) (ys :: [Type]) | xs -> ys where
rigify :: HList xs -> Representation ys
soften :: Representation ys -> HList xs
instance Rigible '[] '[] where
rigify HEmpty = NewRep
soften NewRep = HEmpty
instance (Ord h, Rigible t t') => Rigible (Set h ': t) (h ': t') where
rigify (a :> b) = AddAttribute a $ rigify b
soften (AddAttribute a b) = a :> soften b