在异构列表之间转换

时间:2018-07-31 02:12:47

标签: haskell type-level-computation

我有两个异构的列表结构。第一个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,然后再返回。这样,我可以使用所有相关功能而不必重新定义它们。所以我开始这样做。制作从RepresentationHList 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)

3 个答案:

答案 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)

  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)
    
  2. 您不能用这种类型的文字来写rigifysoften会丢弃存储在每个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
    

    尽管我会选择前者,因为它更简洁。

  3. 使用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
    

    对于每个列表xsSpine 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