2016-02-15 66 views
2

我正在尝试了解如何使用GHC.Generics。一个迷人的话题,但令人生畏。了解如何构建GHC.Generics Rep并将其转换回值

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

但是,读博客条目Building data constructors with GHC Generics描述了构建Rep的模拟并将其转换回一个值,我很难过。我已阅读anumberofotherresources,但没有太大的帮助。

在博客条目中是以下代码。首先,构建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返回?为什么它是仿函数?其结果的解释是什么?我可以看到MkK1 i c实例返回一个函数(我明白这是一个仿函数),它取值为K1,但mkMk (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是没有意义的我。

很显然,我不是所著的Grokking这些实例的目的或意义以及如何将这些实例进行交互以创建最终Rep。我希望有人能够启发我,并提供一个很好的解释如何使用GHC.Generics一路上。

+2

请注意,这里有两个独立的设计“目标”(可以这么说) - 一般地推导一个函数,并使该函数“很好”。作者想编写一个多变量函数,比如'a - > b - > ... - > R',而不是有一个'Compose'链,这就是'Mk'类生成的:'Compose(( - >) a)(撰写(( - >)b)... R'。如果你想了解泛型,我认为你可以忽略除Mk类之外的所有其他类,这是泛型的主力,不知道为什么作者选择使用Compose编写所有内容,然后将其“转换”为多变量函数。 – user2407038

回答

1
  1. 什么是mk返回?

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

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

通过定义Encode'实例为每一个代表类型(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上下文,则无法推导出:*::+:的实例,并且该函数将不再适用于任何嵌套数据类型。

问题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,即在最后->之前一切a -> b -> c -> ... -> rep p

  • 我猜测Compose来自Data.Functor.Compose,这意味着,当我做fmap f x,它做fmap两层深入构成仿函数。但我无法理解Compose中嵌套的fmap
  • :*: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仅改变最内型嵌套撰写的,在这种情况下改变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') 
    
  • 对于M1 i c f的情况下,我还以为它只是将内部值包含在M1中,因此需要M1 <$> mkfmap M1 mk对我没有意义。
  • 它不只是包装内值M1,但目前还不清楚底层f的参数列表有多长。如果只有一个参数,那么mk是一个函数,否则它是一个Compose。 fmap包装它们的最内在价值。

    相关问题