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

时间:2016-02-15 21:19:37

标签: haskell generics ghc ghc-generics

我正在尝试了解如何使用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返回了一个仿函数。我的问题:

  1. mk返回什么?为什么它是一个算符?对结果的解释是什么?我可以看到K1 i c Mk实例返回一个函数(我理解这是一个函子),它接受一个值并将其包装在K1中,但是mk表示{ {1}}和Mk (l :*: r)完全迷失在我身上。

  2. 我猜Mk (M1 i c f)来自Compose,这意味着当我执行Data.Functor.Compose时,它会在fmap f x两个级别深入编写仿函数。但是我无法理解fmap中嵌套的fmap

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

  4. 显然,我并没有意识到这些实例的意图或含义,以及这些实例如何相互作用以创建最终的fmap M1 mk。我希望有人可以启发我并提供一个很好的解释,说明如何在此过程中使用Rep

1 个答案:

答案 0 :(得分:1)

  
      
  1. 什么是contains返回?
  2.   

让我们先来看一个更简单的例子。首先是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

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

  1. class Mk rep where mk :: (? -> p) -- what should '?' be? ,haskell中的函数类型一次只接受一个参数,因此如果构造函数接受多个参数,->将无法返回任何合理的参数。
  2. 参数的数量和类型不清楚:它与所关注的mk类型有关。
  3. 结果类型不能是rep。如果没有p上下文,则无法为rep:*:派生实例,并且该函数将不再适用于任何嵌套数据类型。
  4. 可以使用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) 是对fCompose f g的概括,其中包含构建(->) a的类型级别信息,即最终rep p之前的所有内容->

      
        
    1. 我猜a -> b -> c -> ... -> rep p来自Compose,这意味着当我执行Data.Functor.Compose时,它会在fmap f x两个级别深入到编写的仿函数中。但是我无法理解fmap中嵌套的fmap
    2.   

    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这里实际上是连接两个参数列表mkfl,将结果放入产品类型,即

    fr
      
        
    1. 对于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中,因此M1M1 <$> mk的需要对我没有意义。
    2.   

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