我正在尝试学习如何使用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)))
哇 .
真的,我一开始就陷入困境,在 Mk
, mk
返回一个仿函数 . 我的问题:
-
什么是
mk
返回?为什么它是一个算符?对结果的解释是什么?我可以看到Mk
的K1 i c
实例返回一个函数(我理解这是一个函子),它接受一个值并将其包装在K1
中,但mk
forMk (l :*: r)
和Mk (M1 i c f)
完全丢失了 . -
我猜
Compose
来自Data.Functor.Compose
,这意味着当我做fmap f x
时,它会在组合仿函数中深入到两个级别 . 但我无法理解Compose
中嵌套的fmap
. -
对于
M1 i c f
的实例,我认为它只会将内部值包装在M1
中,因此需要M1 <$> mk
或fmap M1 mk
对我没有意义 .
显然,我并没有意识到这些实例的意图或含义以及这些实例如何相互作用以创建最终的 Rep
. 我希望有人可以启发我,并提供一个很好的解释,如何使用 GHC.Generics
.
1 回答
让我们先来看一个更简单的例子 . 首先是GHC.Generics的文档 . 为了实现一个泛型函数
encode :: Generic a => a -> [Bool]
,该序列化每个具有Generic实例的数据类型,他们定义了下面的类型类:通过为每个Rep类型(M1,K1等)定义
Encode'
实例,它们使该函数在每种数据类型上普遍适用 .在Building data constructors with GHC Generics中,作者的最终目标是通用函数
make :: Generic a => TypeOfConstructor a
,所以天真地可以定义:很快就意识到由于一些问题,这是不可能的:
->
,haskell中的函数类型一次只接受一个参数,因此如果构造函数接受多个参数,mk
将无法返回任何合理的参数 .参数的数量和类型不清楚:它与所关注的
rep
类型有关 .结果类型不能是
p
. 如果没有rep
上下文,则无法为:*:
或:+:
派生实例,并且该函数将不再适用于任何嵌套数据类型 .可以使用Data.Functor.Compose解决问题1 . 类型
a -> b -> c
的函数可以编码为Compose ((->) a) ((->) b) c
,它可以进一步组合,同时保留有关参数类型的大量信息 . 通过使其成为Mk
的类型参数,问题2也得到了解决:其中
f
是Compose f g
和(->) a
的泛化,其中包含构造rep p
的类型级信息,即a -> b -> c -> ... -> rep p
中最后->
之前的所有内容 .在
:*:
的Mk
实例中:fmap
仅更改嵌套Compose的最内层类型,在这种情况下更改n-ary函数的最终结果 .mk
这里实际上是连接两个参数列表fl
和fr
,将它们的结果放入产品类型,即它只是将内部值包装在
M1
中,但不清楚底层f
的参数列表有多长 . 如果它需要一个参数,则mk
是一个函数,否则它是一个Compose .fmap
包装了它们的最内部值 .