2

我正在尝试学习如何使用GHC.Generics. 一个有趣但令人生畏的话题。

在阅读博客文章24 Days of GHC Extensions: DeriveGeneric时,我学会了如何获取一个值并浏览它的Rep. 好的。

然而,阅读博客文章Building data constructors with GHC Generics描述了构造Rep和将其转换回一个值的类比,我被难住了。我已经阅读 许多 其他资源 但没有太大帮助。

在博客条目中是以下代码。首先,构建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)))

哇。

真的,我一开始就被困在返回函子的Mk班级mk。我的问题:

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

  2. 我猜Compose来自Data.Functor.Compose,这意味着当我这样做时fmap f x,它会将fmap两个级别深入到组合的函子中。但我无法fmap理解Compose.

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

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

4

1 回答 1

2
  1. 什么是mk回归?

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

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

Encode'通过为每种 Rep 类型(M1、K1 等)定义实例,他们使该函数在每种数据类型上通用。

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

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

很快就意识到这是不可能的,因为有几个问题:

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

问题 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)

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

  1. 我猜Compose来自Data.Functor.Compose,这意味着当我这样做时fmap f x,它会将fmap两个级别深入到组合的函子中。但我无法fmap理解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. 对于 的实例M1 i c f,我认为它只会将内部值包装在 中M1,因此需要M1 <$> mkfmap M1 mk对我来说没有意义。

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

于 2016-02-16T05:25:24.227 回答