镜头,fclabels,数据访问器 - 用于结构访问和突变的库更好 [英] lenses, fclabels, data-accessor - which library for structure access and mutation is better

查看:110
本文介绍了镜头,fclabels,数据访问器 - 用于结构访问和突变的库更好的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

至少有三个流行的图书馆用于访问和操纵记录领域。我所知道的是:数据访问器,fclabels和镜头。



个人我从数据访问器开始,现在我正在使用它们。不过最近,在haskell-cafe上有一个fclabels的优势。



所以我有兴趣比较这三个(也许更多)的库。 >

解决方案

至少有4个图书馆,我知道提供镜头。



镜头的概念是它提供了同样的东西。

 数据镜头ab =镜头(a  - > b)( b  - > a  - > a)

提供两个功能:一个getter和一个setter

  get(Lens g _)= g 
put(Lens _ s)= s

根据三项法律:



首先,如果你把东西,你可以得到它退出

  get l(put lba)= b 
/ pre>

第二,获取然后设置不会更改答案

  put l(get la)a = a 

第三,putt两次与放一次相反,或者说是第二次胜利。

  put l b1(put l b2 a )= put l b1 a 

请注意,类型系统不足以检查这些法律你,所以你需要确保他们自己,无论你使用什么镜头实现。



许多这些库还提供了一堆额外的组合器在顶部,通常是一些形式为了自动生成简单记录类型的镜头,可以使用模板标签机器。



考虑到这一点,我们可以转到不同的实现:



实施



fclabels



fclabels 可能是最容易理解的的镜头库,因为它的 a: - > b 可以直接转换为上述类型。它提供了一个类别实例为(: - >)这是有用的,因为它允许您组合镜头。它还提供了一个无法无天的点,它概括了这里使用的镜头的概念,以及一些用于处理同构的管道。



采用 fclabels 的一个障碍是,主包包括模板 - haskell管道,所以包不是Haskell 98,它也需要(公平无争议) TypeOperators 扩展名。



数据访问器





data-accessor fclabels 更受欢迎,部分原因是它是Haskell 98.但是,它的选择内部表示使我在我的嘴里吐了一下。



类型 T 它用于表示镜头内部定义为

  newtype T ra = Cons {decons :: a  - > r  - > (a,r)} 

因此,为了获取镜头的值,您必须为'a'参数提交未定义的值!这使我感到非常丑陋和特别的实现。



那就是说,Henning已经包含了模板 - haskell管道,以单独的'一个href =http://hackage.haskell.org/package/data-accessor-template =nofollow noreferrer> data-accessor-template '包。



它具有已经使用它的正式大型软件包的好处,它是Haskell 98,并且提供了非常重要的类别实例,因此如果你不会注意如何制作香肠,这个包实际上是很合理的选择。



镜头



接下来,有一个透镜包,其中观察到透镜可以在两个状态单子之间提供状态单子同态,通过将透镜直接定义为这样的monad homo态度。



如果实际上打扰了为其镜头提供一种类型,那么他们会有一个等级2类型,如:

  newtype Lens st = Lens(forall a。状态 - >状态sa)

因此,我宁愿不喜欢这种方法,因为它不必要地嘲笑你从Haskell 98(如果你想要一个类型提供给你的镜头在抽象),并剥夺你的镜头的类别实例,这将让你组合它们与。实现也需要多参数类型类。



注意,这里提到的所有其他镜头库提供了一些组合器,或者可以用于提供相同的状态聚焦效果,所以没有什么可以通过以这种方式直接编码你的镜头来获得。



此外,开头说明的边条件在这种形式上并没有真正的表达。与'fclabels'一样,它提供了用于在主包中直接自动生成记录类型的镜头的模板 - haskell方法。



由于缺少类别实例,巴洛克式编码以及主包中的template-haskell的要求,这是我最不喜欢的实现。



数据透镜



p>

我的 data-镜头 包提供的镜头根据 Store comonad。

  newtype Lens ab = Lens(a  - > Store ba)

其中

 数据存储ba = St矿石(b  - > a)b 

扩展相当于

  newtype镜头ab =镜头(a  - >(b,b  - > a))

您可以将其视为从getter和setter中分离出常见参数,以返回由检索元素的结果组成的对,以及一个设置器以重新输入新值。这提供了setter在这里可以回收一些用来获取价值的工作的计算好处,从而使得比codefclabels 更有效的修改操作定义,特别是当访问者被链接时。



对于这种表示,还有一个很好的理论依据,因为镜头值的子集满足了这种反应的开始正是这些镜头,其中包装功能是商店联合会的联合组织。这样就可以将镜头的3条毛茸茸的法律转换成2个不错的无符号等价物:

  extract。 l = id 
重复。 l = fmap l。 l

这种方法首先在Russell O'Connor的 Functor 镜头 as 适用 Biplate :介绍Multiplate ,并且是根据Jeremy Gibbons的预印本发表。 >

它还包括一些用于严格使用镜头的组合器和一些集装箱的镜片,例如 Data.Map



所以数据透镜中的镜头形成一个类别镜头包),是Haskell 98(不像 fclabels / code>),是理智的(与 data-accessor 的后端不同),并提供稍微更有效的实现, data-lens-fd 提供使用MonadState的功能对于那些愿意在Haskell 98以外的人来说,模板化的机器现在可以通过 data-lens-template



更新6/28/2012 :其他镜头实施策略



同构镜片



<另外还有另外两个镜头编码值得考虑。第一个给出了一个很好的理论方法来查看镜头,将一个结构打破了这个领域的价值以及一切。



给定一个类型同构异构

  data Iso ab = Iso {hither :: a  - > b,yon :: b  - > a} 

,使有效成员满足。 yon = id yon。 hither = id



我们可以代表一个镜头:

 数据镜头ab = forall c。镜头(Iso a(b,c))

这些主要用于考虑透镜的意义,我们可以使用它们作为解释其他镜头的推理工具。



van Laarhoven镜头



我们可以对镜头进行建模,使其可以由(。) id ,即使没有类别实例,使用

  type Lens ab = forall F。函子f => (b→f b)→> a  - > fa 

作为我们镜头的类型。



然后定义一个镜头就像:

  _2 f(a,b)=(,)a&$ > fb 

,您可以自己验证功能组合是镜头组合。



我最近写了一篇关于如何进一步泛化van Laarhoven镜头让透镜家族可以改变字体类型,只需将这个签名推广到

 输入LensFamily abcd = forall f。函子f => (c→f d)→> a  - > fb 

这确实有不幸的后果,谈论镜头的最好方法是使用2级多态,但您不需要在定义镜头时直接使用该签名。



我定义的镜头 _2 实际上是一个 LensFamily

  _2 :: Functor f => (a→f b)→> (c,a)→> f(c,b)

我写了一个图书馆,包括镜头,镜头家庭等概括,包括getter,setter,folds和遍历。 镜头 软件包



同样,这种方法的一大优点是,库维护者实际上可以在您的库中创建这种风格的镜头,而不会产生任何镜头库依赖性提供类型为 Functor f => (b→f b)→> a - > f a ,其特定类型为a和b。这大大降低了采用的成本。



由于您不需要实际使用该软件包来定义新的镜头,因此我之前关注的问题需要很大的压力保存图书馆Haskell 98。


There are at least three popular libraries for accessing and manipulating fields of records. The ones I know of are: data-accessor, fclabels and lenses.

Personally I started with data-accessor and I'm using them now. However recently on haskell-cafe there was an opinion of fclabels being superior.

Therefore I'm interested in comparison of those three (and maybe more) libraries.

解决方案

There are at least 4 libraries that I am aware of providing lenses.

The notion of a lens is that it provides something isomorphic to

data Lens a b = Lens (a -> b) (b -> a -> a)

providing two functions: a getter, and a setter

get (Lens g _) = g
put (Lens _ s) = s

subject to three laws:

First, that if you put something, you can get it back out

get l (put l b a) = b 

Second that getting and then setting doesn't change the answer

put l (get l a) a = a

And third, putting twice is the same as putting once, or rather, that the second put wins.

put l b1 (put l b2 a) = put l b1 a

Note, that the type system isn't sufficient to check these laws for you, so you need to ensure them yourself no matter what lens implementation you use.

Many of these libraries also provide a bunch of extra combinators on top, and usually some form of template haskell machinery to automatically generate lenses for the fields of simple record types.

With that in mind, we can turn to the different implementations:

Implementations

fclabels

fclabels is perhaps the most easily reasoned about of the lens libraries, because its a :-> b can be directly translated to the above type. It provides a Category instance for (:->) which is useful as it allows you to compose lenses. It also provides a lawless Point type which generalizes the notion of a lens used here, and some plumbing for dealing with isomorphisms.

One hindrance to the adoption of fclabels is that the main package includes the template-haskell plumbing, so the package is not Haskell 98, and it also requires the (fairly non-controversial) TypeOperators extension.

data-accessor

[Edit: data-accessor is no longer using this representation, but has moved to a form similar to that of data-lens. I'm keeping this commentary, though.]

data-accessor is somewhat more popular than fclabels, in part because it is Haskell 98. However, its choice of internal representation makes me throw up in my mouth a little bit.

The type T it uses to represent a lens is internally defined as

newtype T r a = Cons { decons :: a -> r -> (a, r) }

Consequently, in order to get the value of a lens, you must submit an undefined value for the 'a' argument! This strikes me as an incredibly ugly and ad hoc implementation.

That said, Henning has included the template-haskell plumbing to automatically generate the accessors for you in a separate 'data-accessor-template' package.

It has the benefit of a decently large set of packages that already employ it, being Haskell 98, and providing the all-important Category instance, so if you don't pay attention to how the sausage is made, this package is actually pretty reasonable choice.

lenses

Next, there is the lenses package, which observes that a lens can provide a state monad homomorphism between two state monads, by definining lenses directly as such monad homomorphisms.

If it actually bothered to provide a type for its lenses, they would have a rank-2 type like:

newtype Lens s t = Lens (forall a. State t a -> State s a)

As a result, I rather don't like this approach, as it needlessly yanks you out of Haskell 98 (if you want a type to provide to your lenses in the abstract) and deprives you of the Category instance for lenses, which would let you compose them with .. The implementation also requires multi-parameter type classes.

Note, all of the other lens libraries mentioned here provide some combinator or can be used to provide this same state focalization effect, so nothing is gained by encoding your lens directly in this fashion.

Furthermore, the side-conditions stated at the start don't really have a nice expression in this form. As with 'fclabels' this does provide template-haskell method for automatically generating lenses for a record type directly in the main package.

Because of the lack of Category instance, the baroque encoding, and the requirement of template-haskell in the main package, this is my least favorite implementation.

data-lens

[Edit: As of 1.8.0, these have moved from the comonad-transformers package to data-lens]

My data-lens package provides lenses in terms of the Store comonad.

newtype Lens a b = Lens (a -> Store b a)

where

data Store b a = Store (b -> a) b

Expanded this is equivalent to

newtype Lens a b = Lens (a -> (b, b -> a))

You can view this as factoring out the common argument from the getter and the setter to return a pair consisting of the result of retrieving the element, and a setter to put a new value back in. This offers the computational benefit that the 'setter' here can recycle some of the work used to get the value out, making for a more efficient 'modify' operation than in the fclabels definition, especially when accessors are chained.

There is also a nice theoretical justification for this representation, because the subset of 'Lens' values that satisfy the 3 laws stated in the beginning of this response are precisely those lenses for which the wrapped function is a 'comonad coalgebra' for the store comonad. This transforms 3 hairy laws for a lens l down to 2 nicely pointfree equivalents:

extract . l = id
duplicate . l = fmap l . l

This approach was first noted and described in Russell O'Connor's Functor is to Lens as Applicative is to Biplate: Introducing Multiplate and was blogged about based on a preprint by Jeremy Gibbons.

It also includes a number of combinators for working with lenses strictly and some stock lenses for containers, such as Data.Map.

So the lenses in data-lens form a Category (unlike the lenses package), are Haskell 98 (unlike fclabels/lenses), are sane (unlike the back end of data-accessor) and provide a slightly more efficient implementation, data-lens-fd provides the functionality for working with MonadState for those willing to step outside of Haskell 98, and the template-haskell machinery is now available via data-lens-template.

Update 6/28/2012: Other Lens Implementation Strategies

Isomorphism Lenses

There are two other lens encodings worth considering. The first gives a nice theoretical way to view a lens as a way to break a structure into the value of the field, and 'everything else'.

Given a type for isomorphisms

data Iso a b = Iso { hither :: a -> b, yon :: b -> a }

such that valid members satisfy hither . yon = id, and yon . hither = id

We can represent a lens with:

data Lens a b = forall c. Lens (Iso a (b,c))

These are primarily useful as a way to think about the meaning of lenses, and we can use them as a reasoning tool to explain other lenses.

van Laarhoven Lenses

We can model lenses such that they can be composed with (.) and id, even without a Category instance by using

type Lens a b = forall f. Functor f => (b -> f b) -> a -> f a

as the type for our lenses.

Then defining a lens is as easy as:

_2 f (a,b) = (,) a <$> f b

and you can validate for yourself that function composition is lens composition.

I've recently written on how you can further generalize van Laarhoven lenses to get lens families that can change the types of fields, just by generalizing this signature to

type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b

This does have the unfortunate consequence that the best way to talk about lenses is to use rank 2 polymorphism, but you don't need to use that signature directly when defining lenses.

The Lens I defined above for _2 is actually a LensFamily.

_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)

I've written a library that includes lenses, lens families, and other generalizations including getters, setters, folds and traversals. It is available on hackage as the lens package.

Again, a big advantage of this approach is that library maintainers can actually create lenses in this style in your libraries without incurring any lens library dependency whatsoever, by just supplying functions with type Functor f => (b -> f b) -> a -> f a, for their particular types 'a' and 'b'. This greatly lowers the cost of adoption.

Since you don't need to actually use the package to define new lenses, it takes a lot of pressure off my earlier concerns about keeping the library Haskell 98.

这篇关于镜头,fclabels,数据访问器 - 用于结构访问和突变的库更好的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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