异构List的映射操作

时间:2017-10-14 22:56:34

标签: haskell typeclass functor

我有一个简单的异类List

data HList (ts :: [Type]) where
    HNil :: HList '[]
    (:&:) :: t -> HList ts -> HList (t ': ts)

infixr 5 :&:

我正在尝试定义异构地图:

type family ToDomain (xs :: [Type]) :: [Type] where
    ToDomain '[] = '[];
    ToDomain ((a -> b) ': r) = a ': ToDomain r

type family ToCoDomain (xs :: [Type]) :: [Type] where
    ToCoDomain '[] = '[];
    ToCoDomain ((a -> b) ': r) = b ': ToCoDomain r

mapH :: forall mappings input output.
    (input ~ ToDomain mappings, output ~ ToCoDomain mappings)
    => HList mappings -> HList input -> HList output
mapH HNil HNil = HNil
mapH (f :&: maps) (x :&: ins) = (f x) :&: mapH maps ins

这不会编译(如果这实际上有效,我会感到非常惊讶),因为GHC无法推断递归的正确性。我不确定如何说服GHC的正确性。

2 个答案:

答案 0 :(得分:2)

您需要证明mappings内部只有函数。这可以通过额外的GADT进行。

data OnlyFunctions (ts :: [*]) where
   FNil :: OnlyFunctions '[]
   FCons :: OnlyFunctions ts -> OnlyFunctions ((a->b) ': ts)

mapH :: forall mappings input output.
    (input ~ ToDomain mappings, output ~ ToCoDomain mappings)
    => OnlyFunctions mappings -> HList mappings -> HList input -> HList output
mapH FNil HNil HNil = HNil
mapH (FCons h) (f :&: maps) (x :&: ins) = (f x) :&: mapH h maps ins

问题是ToDomain '[Int,Bool]是有效类型 - 即使ToDomain在这种情况下不简化。因此,GHC不能假设mappings类型列表仅由函数组成。

如果需要,您可以通过自定义类型类删除其他参数。

class OF (ts :: [*]) where
   onlyFun :: OnlyFunctions ts
instance OF '[] where
   onlyFun = FNil
instance OF ts => OF ((a -> b) ': ts) where
   onlyFun = FCons onlyFun

mapHsimple :: forall mappings input output.
    (input ~ ToDomain mappings, output ~ ToCoDomain mappings, OF mappings)
    => HList mappings -> HList input -> HList output
mapHsimple = mapH onlyFun

答案 1 :(得分:2)

如果你想进行地图操作,你几乎肯定会更好地定义

data Rec (f :: k -> *) (ts :: [k]) where
  RNil :: Rec f '[]
  (:&:) :: f t -> Rec f ts -> Rec f (t ': ts)

infixr 5 :&:

现在你可以定义一般的

rmap :: (forall a. f a -> g a) -> Rec f ts -> Rec g ts

rzipWith :: (forall a. f a -> g a -> h a) -> Rec f ts -> Rec g ts -> Rec h ts

这通常会让事情变得更加容易。在这种情况下,您可以使用

data Fun :: * -> * where
  Fun :: (a -> b) -> Fun (a -> b)

data Dom :: * -> * where
  Dom :: a -> Dom (a -> b)

data Cod :: * -> * where
  Cod :: b -> Cod (a -> b)

现在你可以写

zoop :: Rec Fun fs -> Rec Dom fs -> Rec Cod fs
zoop = rzipWith (\(Fun f) (Dom x) -> Cod (f x))

事实上,其中一些是轻微的过度杀伤;你应该能够使用包裹Dom类型系列的newtype来逃脱。您甚至可以为Cod执行此操作,但这可能会降低结果的可用性。