首页 文章

了解如何构建GHC.Generics Rep并转换回值

提问于
浏览
2

我正在尝试学习如何使用GHC.Generics . 一个引人入胜的话题,但令人生畏 .

在阅读博客条目24 Days of GHC Extensions: DeriveGeneric时,我学会了如何获取值并导航其 Rep . 好的 .

但是,阅读博客条目Building data constructors with GHC Generics,它描述了构建 Rep 并将其转换回值的模拟,我感到难过 . 我已经阅读了a number of other resources,但没有太大的帮助 .

在博客条目中是以下代码 . 首先,构建 Rep

class Functor f => Mk rep f | rep -> f where
  mk :: f (rep a)

instance Mk (K1 i c) ((->) c) where
  mk = \x -> K1 x

instance (Mk l fl, Mk r fr) => Mk (l :*: r) (Compose fl fr) where
  mk = Compose (fmap (\l -> fmap (\r -> l :*: r) mk) mk)

instance (Mk f f') => Mk (M1 i c f) f' where
  mk = M1 <$> mk

然后,处理 Compose

class Functor f => Apply f a b | f a -> b where
  apply :: f a -> b

instance Apply ((->) a) b (a -> b) where
  apply = id

instance (Apply g a b, Apply f b c) => Apply (Compose f g) a c where
  apply (Compose x) = apply (fmap apply x)

然后处理类型歧义:

type family Returns (f :: *) :: * where
  Returns (a -> b) = Returns b
  Returns r = r

make :: forall b f z. (Generic (Returns b), Apply f (Returns b) b, Mk (Rep (Returns b)) f) => b
make = apply (fmap (to :: Rep (Returns b) z -> (Returns b)) (mk :: f (Rep (Returns b) z)))

哇 .

真的,我一开始就陷入困境,在 Mkmk 返回一个仿函数 . 我的问题:

  • 什么是 mk 返回?为什么它是一个算符?对结果的解释是什么?我可以看到 MkK1 i c 实例返回一个函数(我理解这是一个函子),它接受一个值并将其包装在 K1 中,但 mk for Mk (l :*: r)Mk (M1 i c f) 完全丢失了 .

  • 我猜 Compose 来自 Data.Functor.Compose ,这意味着当我做 fmap f x 时,它会在组合仿函数中深入到两个级别 . 但我无法理解 Compose 中嵌套的 fmap .

  • 对于 M1 i c f 的实例,我认为它只会将内部值包装在 M1 中,因此需要 M1 <$> mkfmap M1 mk 对我没有意义 .

显然,我并没有意识到这些实例的意图或含义以及这些实例如何相互作用以创建最终的 Rep . 我希望有人可以启发我,并提供一个很好的解释,如何使用 GHC.Generics .

1 回答

  • 1

    什么是mk返回?

    让我们先来看一个更简单的例子 . 首先是GHC.Generics的文档 . 为了实现一个泛型函数 encode :: Generic a => a -> [Bool] ,该序列化每个具有Generic实例的数据类型,他们定义了下面的类型类:

    class Encode' rep where
      encode' :: rep p -> [Bool]
    

    通过为每个Rep类型(M1,K1等)定义 Encode' 实例,它们使该函数在每种数据类型上普遍适用 .

    Building data constructors with GHC Generics中,作者的最终目标是通用函数 make :: Generic a => TypeOfConstructor a ,所以天真地可以定义:

    class Mk rep where
      mk :: (? -> p) -- what should '?' be?
    

    很快就意识到由于一些问题,这是不可能的:

    • -> ,haskell中的函数类型一次只接受一个参数,因此如果构造函数接受多个参数, mk 将无法返回任何合理的参数 .

    • 参数的数量和类型不清楚:它与所关注的 rep 类型有关 .

    • 结果类型不能是 p . 如果没有 rep 上下文,则无法为 :*::+: 派生实例,并且该函数将不再适用于任何嵌套数据类型 .

    可以使用Data.Functor.Compose解决问题1 . 类型 a -> b -> c 的函数可以编码为 Compose ((->) a) ((->) b) c ,它可以进一步组合,同时保留有关参数类型的大量信息 . 通过使其成为 Mk 的类型参数,问题2也得到了解决:

    class Functor f => Mk rep f | rep -> f where
      mk :: f (rep p)
    

    其中 fCompose f g(->) a 的泛化,其中包含构造 rep p 的类型级信息,即 a -> b -> c -> ... -> rep p 中最后 -> 之前的所有内容 .

    我猜测Compose来自Data.Functor.Compose,这意味着当我执行fmap f x时,它会将fmap的两个级别深入到组合的仿函数中 . 但我无法理解Compose中嵌套的fmaps .

    :*:Mk 实例中:

    instance (Mk l fl, Mk r fr) => Mk (l :*: r) (Compose fl fr) where
      mk = Compose (fmap (\l -> fmap (\r -> l :*: r) mk) mk)
    

    fmap 仅更改嵌套Compose的最内层类型,在这种情况下更改n-ary函数的最终结果 . mk 这里实际上是连接两个参数列表 flfr ,将它们的结果放入产品类型,即

    f :: Compose ((->) a) ((->) b) (f r)
    g :: Compose ((->) c) ((->) d) (g r)
    mk f g :: Compose (Compose ((->) a) ((->) b)) (Compose ((->) c) ((->) d)) ((:*:) f g r)
    
    -- or unwrapped and simplified
    (a -> b -> r) -> (c -> d -> r') -> a -> b -> c -> d -> (r, r')
    

    对于M1 i c f的实例,我认为它只会将内部值包装在M1中,因此需要M1 <$> mk或fmap M1 mk对我没有意义 .

    它只是将内部值包装在 M1 中,但不清楚底层 f 的参数列表有多长 . 如果它需要一个参数,则 mk 是一个函数,否则它是一个Compose . fmap 包装了它们的最内部值 .

相关问题