了解如何构建GHC.Generics Rep并将其转换回值 [英] Understanding how to construct GHC.Generics Rep's and convert back to values

查看:120
本文介绍了了解如何构建GHC.Generics Rep并将其转换回值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图了解如何使用 GHC.Generics 。一个引人入胜的话题,但令人望而生畏。

在阅读博客文章 24天的GHC扩展:DeriveGeneric ,我学会了如何获取一个值并导航它的 Rep 。好的。

但是,阅读博客条目构建具有GHC Generics的数据构造函数,它描述了构建 Rep 的类比并将其转换回一个值,我被困住了。我已阅读 a 数字 of 其他 资源,但没有太大的帮助。

在博客条目中有以下代码。首先,构造 Rep

  class Functor f => Mk rep f | rep  - > f其中
mk :: f(rep a)

instance Mk(K1 i c)(( - >)c)其中
mk = \ x - > K1 x

实例(Mk l fl,Mk r fr)=> Mk(l:*:r)(Compose fl fr)其中
mk =撰写(fmap(\l-> fmap(\r-> l:*:r)mk)mk)

instance(Mk f f')=> Mk(M1 i c f)f'其中
mk = M1< $> mk

然后,处理 Compose

  class Functor f =>应用f a b | f a  - > b其中
适用:: f a - > b

实例应用(( - >)a)b(a - > b)其中
apply = id

实例(应用gab,Apply fbc )=>应用(Compose fg)ac其中
适用(Compose x)= apply(fmap apply x)

然后处理类型歧义:

  type family返回(f :: *):: *其中
返回(a - > b)=回报b
回报r = r

make :: forall bf z。 (通用(返回b),应用f(返回b)b,Mk(Rep(返回b))f)=> b
make = apply(fmap(to :: Rep(Returns b)z - >(Returns b))(mk :: f(Rep(Returns b)z)))

哇。



真的,我一开始就停留在课程 Mk 其中 mk 返回一个函子。我的问题:


  1. 什么是 mk 为什么它是仿函数?其结果的解释是什么?我可以看到 K1 ic 实例 Mk 返回一个函数(我明白这是一个函数),它需要一个函数值并将其封装在 K1 中,但是 mk 对于 Mk(l:*:r) Mk(M1 icf)完全不在我身上。

  2. 我在猜测 Compose 来自 Data.Functor.Compose ,这意味着当我做 fmap fx ,它将 fmap 深入到组合函子的两层。但我无法理解 Compose 中的嵌套 fmap s。
  3. 对于 M1 icf 的实例,我认为它会将内部值包装在 M1 ,所以需要 M1< $> mk fmap M1 mk 对我没有意义。


很明显,我并没有对这些实例的意图或含义以及这些实例如何相互作用来创建最终的 Rep 。我希望有人能够启发我,并提供一个很好的解释,说明如何在使用 GHC.Generics 的过程中使用它。



  1. 什么是 mk 正在返回?


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

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

通过定义 Encode'实例对于每种Rep类型(M1,K1等),他们都会在每种数据类型上普遍使用该函数。

.org.uk / blog / posts / 2014-04-26-construct-generically.htmlrel =nofollow>使用GHC Generics构建数据构造函数,作者的最终目标是一个通用函数 make :: Generic a =>

  class Mk rep其中
mk: :(? - > p) - 应该是什么?

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


  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其中
mk :: f(rep p)

其中 f 是对撰写fg ( - >)a 的概括,其中包含类型(code>)代表 - > 之前的所有内容来构建 a - > b - > c - > ... - >代表



  1. 我在猜测撰写来自 Data.Functor.Compose ,这意味着当我做 fmap fx 时,它会执行 fmap 组合函子深入两层。但我无法理解 Compose 中的嵌套 fmap s。


Mk code>:*:

  instance(Mk l fl,Mk r fr) => Mk(l:*:r)(Compose fl fr)其中
mk =撰写(fmap(\l-> fmap(\r-> l:*:r)mk)mk)

fmap 仅更改最内层类型的嵌套Compose,在这种情况下改变n元函数的最终结果。 mk 这里是从字面上连接两个参数列表 fl fr ,将他们的结果放入产品类型中,即

  f :: Compose(( - >)a)(( - > ;)b)(fr)
g :: Compose(( - >)c)(( - >)d)(gr)
mk fg :: Compose(Compose(( - >) a)(( - >)b))(Compose(( - >)c)(( - >)d))((:* :) fgr)

- 或展开和简化的
(a - > b - > r) - > (c - > d - > r') - > a - > b - > c - > d - > (r,r')





  1. 对于 M1 icf 的实例,我认为它只是将内部值包装在 M1 中,所以需要 M1< $>


它只包含 M1 中的内部值,但不清楚底层<$ c $的参数列表有多长c> f 是。如果只有一个参数,那么 mk 是一个函数,否则它是一个Compose。 fmap 包装它们的最内层价值。


I'm trying to learn about how to use GHC.Generics. A fascinating topic but daunting.

While reading through the blog entry 24 Days of GHC Extensions: DeriveGeneric, I learned how to take a value and navigate its Rep. Okay.

However, reading the blog entry Building data constructors with GHC Generics which describes the analog of constructing the Rep and converting it back to a value, I got stumped. I've read through a number of other resources, but to no great help.

In the blog entry is the following code. First, constructing the 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

Then, dealing with the 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)

Then dealing with type ambiguity:

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

Wow.

Really, I'm stuck at the very beginning, at the class Mk where mk returns a functor. My questions:

  1. What is mk returning? Why is it a functor? What is the interpretation of its result? I can see that the K1 i c instance of Mk returns a function (I understand this is a functor) that takes a value and wraps it in K1, but mk for Mk (l :*: r) and Mk (M1 i c f) are completely lost on me.

  2. I'm guessing Compose comes from Data.Functor.Compose, which means that when I do fmap f x, it does the fmap two levels deep into the composed functors. But I can't make sense of the nested fmaps inside the Compose.

  3. For the instance of M1 i c f, I thought it would just wrap the inner values in M1, so the need to M1 <$> mk or fmap M1 mk makes no sense to me.

Obviously I'm not grokking the intent or meaning of these instances and how these instances interact to create the final Rep. I am hoping someone can enlighten me and provide a good explanation of how to use GHC.Generics along the way.

解决方案

  1. What is mk returning?

Let's go through a much simpler example In the documentation of GHC.Generics first. To achieve a generic function encode :: Generic a => a -> [Bool] that bit serialize every data type which has a Generic instance, they defined the type class below :

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

By defining Encode' instances for every Rep type (M1, K1, etc.), they made the function work universally on every data types.

In Building data constructors with GHC Generics, the author's final goal is a generic function make :: Generic a => TypeOfConstructor a, so naively one may define:

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

And soon realize that it's impossible due to a few problems:

  1. ->, the function type in haskell only takes one argument at a time, so mk won't be able to return anything sensible if the constructor takes more than one argument.
  2. The number and type of the arguments are unclear: it's relative to the rep type in concern.
  3. It cannot be plain p as the result type. Without the rep context it's impossible to derive instances for :*: or :+:, and the function will no longer work on any nested data type.

Issue 1 can be solved with Data.Functor.Compose. A function of type a -> b -> c can be encoded into Compose ((->) a) ((->) b) c, it can be further composed while keeps a whole lot of information about argument types. And by making it a type parameter of Mk, issue 2 is solved too:

class Functor f => Mk rep f | rep -> f where
  mk :: f (rep p)

where f is generalization over Compose f g and (->) a, which contains type-level information to construct a rep p, i.e. everything before the final -> in a -> b -> c -> ... -> rep p.

  1. I'm guessing Compose comes from Data.Functor.Compose, which means that when I do fmap f x, it does the fmap two levels deep into the composed functors. But I can't make sense of the nested fmaps inside the Compose.

In the Mk instance of :*::

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 changes only the inner-most type of a nested Compose, in this case changes the final result of a n-ary function. mk here is literally concatenating two argument lists fl and fr, putting their results into a product type, namely

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. For the instance of M1 i c f, I thought it would just wrap the inner values in M1, so the need to M1 <$> mk or fmap M1 mk makes no sense to me.

It does just wrap the inner values in M1, but it's unclear how long the argument list of the underlying f is. If it takes one argument then mk is a function, otherwise it's a Compose. fmap wraps the inner-most value of them both.

这篇关于了解如何构建GHC.Generics Rep并将其转换回值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆