我正在尝试了解如何使用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
返回什么?为什么它是一个算符?对结果的解释是什么?我可以看到K1 i c
Mk
实例返回一个函数(我理解这是一个函子),它接受一个值并将其包装在K1
中,但是mk
表示{ {1}}和Mk (l :*: r)
完全迷失在我身上。
我猜Mk (M1 i c f)
来自Compose
,这意味着当我执行Data.Functor.Compose
时,它会在fmap f x
两个级别深入编写仿函数。但是我无法理解fmap
中嵌套的fmap
。
对于Compose
的实例,我认为它只会将内部值包装在M1 i c f
中,因此对M1
或M1 <$> mk
的需求毫无意义对我来说。
显然,我并没有意识到这些实例的意图或含义,以及这些实例如何相互作用以创建最终的fmap M1 mk
。我希望有人可以启发我并提供一个很好的解释,说明如何在此过程中使用Rep
。
答案 0 :(得分:1)
- 什么是
醇>contains
返回?
让我们先来看一个更简单的例子。首先是GHC.Generics的文档。为了实现一个通用函数mk
,它对每个具有Generic实例的数据类型进行序列化,他们定义了下面的类型类:
encode :: Generic a => a -> [Bool]
通过为每个Rep类型(M1,K1等)定义class Encode' rep where
encode' :: rep p -> [Bool]
个实例,它们使该函数在每种数据类型上都能正常工作。
在Building data constructors with GHC Generics中,作者的最终目标是通用函数Encode'
,所以天真地可以定义:
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
的类型参数,问题2也得到了解决:
Mk
其中class Functor f => Mk rep f | rep -> f where
mk :: f (rep p)
是对f
和Compose f g
的概括,其中包含构建(->) a
的类型级别信息,即最终rep p
之前的所有内容->
。
- 我猜
醇>a -> b -> c -> ... -> rep p
来自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)
仅更改嵌套Compose的最内层类型,在这种情况下会更改n-ary函数的最终结果。 fmap
这里实际上是连接两个参数列表mk
和fl
,将结果放入产品类型,即
fr
- 对于
醇>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
是一个函数,否则它是一个Compose。 mk
包含了它们最内在的价值。