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

Understanding how to construct GHC.Generics Rep's and convert back to values

我正在尝试了解如何使用 GHC.Generics。一个引人入胜但又令人生畏的话题。

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

然而,阅读博文Building data constructors with GHC Generics which describes the analog of constructing the Rep and converting it back to a value, I got stumped. I've read through 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)))

哇。

真的,我一开始就卡住了,在 class Mk 那里 mk returns 一个仿函数。我的问题:

  1. mk 返回了什么?为什么它是函子?其结果的解释是什么?我可以看到 Mk returns 的 K1 i c 实例是一个函数(我知道这是一个仿函数),它接受一个值并将其包装在 K1 中,但是 mk 对于 Mk (l :*: r)Mk (M1 i c f) 我完全迷失了。

  2. 我猜 Compose 来自 Data.Functor.Compose,这意味着当我做 fmap f x 时,它会做 fmap 两层深进入组成的函子。但是我无法理解 Compose 中嵌套的 fmap

  3. 对于M1 i c f的实例,我认为它只是将内部值包装在M1中,所以需要M1 <$> mkfmap M1 mk 对我来说毫无意义。

显然,我并不是在摸索这些实例的意图或意义,以及这些实例如何相互作用以创建最终的 Rep。我希望有人能启发我并提供一个很好的解释如何使用GHC.Generics

  1. What is mk returning?

让我们先看一下 GHC.Generics 文档中的一个更简单的示例。为了实现通用函数 encode :: Generic a => a -> [Bool] 位序列化每个具有通用实例的数据类型,他们在下面定义了类型 class :

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?

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

  1. ->,haskell 中的函数类型一次只接受一个参数,因此 mk 将无法 return 任何有意义的构造函数接受多个参数。
  2. 参数的数量和类型不清楚:它与所关注的 rep 类型有关。
  3. 结果类型不能是普通的p。如果没有 rep 上下文,就不可能为 :*::+: 派生实例,并且该函数将不再适用于任何嵌套数据类型。

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

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

其中 f 是对 Compose f g(->) a 的泛化,它包含构造 rep p 的类型级信息,即最终 [=19= 之前的所有内容] 在 a -> b -> c -> ... -> rep p

  1. I'm guessing Compose comes from Data.Functor.Compose, which means that when I do fmap f x, it does the fmap two levels deep into the composed functors. But I can't make sense of the nested fmaps inside the Compose.

:*: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 元函数的最终结果。这里的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')
  1. For the instance of M1 i c f, I thought it would just wrap the inner values in M1, so the need to M1 <$> mk or fmap M1 mk makes no sense to me.

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