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

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

问题描述

至少有三个用于访问和操作记录字段的流行库.我知道的有:数据访问器、fclabels 和镜头.

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.

我个人是从数据访问器开始的,现在我正在使用它们.然而最近在 haskell-cafe 上,有人认为 fclabels 更胜一筹.

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.

推荐答案

我知道至少有 4 个库提供镜头.

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)

提供两个函数:getter 和 setter

providing two functions: a getter, and a setter

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

受三项法律约束:

首先,如果你放了东西,你可以把它拿回来

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.

这些库中的许多还在顶部提供了一堆额外的组合器,通常是某种形式的模板 haskell 机制来自动为简单记录类型的字段生成镜头.

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:

实施

fclabels

fclabels 可能是最容易推理的镜头库,因为它的 :->b 可以直接翻译成上面的类型.它提供了一个 Category (:->) 的实例,这很有用,因为它允许您组合镜头.它还提供了一个无法无天的 Point 类型,它概括了这里使用的镜头的概念,以及一些处理同构的管道.

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.

采用 fclabels 的一个障碍是主包包括模板-haskell 管道,所以包不是 Haskell 98,它还需要(相当无争议的)TypeOperators 扩展.

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-accessorfclabels 更受欢迎,部分是因为它 Haskell 98.然而,它对内部表示的选择让我有点吐了.

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.

它用来表示镜头的类型T在内部定义为

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

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

因此,为了get一个镜头的值,你必须为'a'参数提交一个未定义的值!这让我觉得这是一个非常丑陋和临时的实现.

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.

也就是说,Henning 已包含模板-haskell 管道,可在单独的data-accessor-template' 包.

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

它的好处是有大量已经使用它的软件包,即 Haskell 98,并提供了非常重要的 Category 实例,因此如果您不注意香肠就做好了,这个套餐其实还蛮合理的选择.

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 包,它观察到一个镜头可以在两个状态 monad 之间提供一个状态 monad 同态,方法是直接将镜头定义为 这样的 monad 同态.

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.

如果它真的很费心为其镜头提供一种类型,那么它们将具有 2 级类型,例如:

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)

因此,我宁愿不喜欢这种方法,因为它不必要地将您从 Haskell 98 中拉出(如果您想在抽象中为您的镜头提供一种类型)并剥夺您的 Category 镜头的实例,它可以让你用 . 组合它们.实现还需要多参数类型类.

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.

此外,开头所述的附带条件在这种形式中并没有很好的表达.与fclabels"一样,这确实提供了模板-haskell 方法,用于直接在主包中为记录类型自动生成镜头.

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.

由于缺少Category 实例、巴洛克式编码以及主包中模板-haskell 的要求,这是我最不喜欢的实现.

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 包提供了镜头存储 comonad.

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

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

哪里

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

展开这相当于

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

您可以将其视为从 getter 和 setter 中分解出公共参数,以返回由检索元素的结果和一个将新值重新放入的 setter 组成的对.这提供了计算优势,即此处的setter"可以回收一些用于获取值的工作,从而使修改"操作比在 fclabels 定义中更有效,尤其是在访问器被链接时.

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.

这种表示还有一个很好的理论依据,因为满足此响应开头所述的 3 个定律的镜头"值的子集正是那些包装函数是共子合代数"的镜头商店comonad.这将镜头 l 的 3 个多毛定律转换为 2 个很好的无点等效项:

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

在 Russell O'Connor 的 Functor 中首次注意到并描述了这种方法Lens 作为 Applicative 是对 Biplate: Introducing Multiplate根据 Jeremy Gibbons 的预印本撰写了博客.

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.

它还包括许多用于严格处理镜头的组合器和一些用于容器的库存镜头,例如 Data.Map.

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

所以data-lens中的lens形成一个Category(不同于lenses包),是Haskell 98(不同于fclabels/lenses),是健全的(与 data-accessor 的后端不同)并提供稍微更有效的实现,data-lens-fd 为那些愿意走出去的人提供使用 MonadState 的功能Haskell 98 的模板,现在可以通过 data-lens-模板.

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.

2012 年 6 月 28 日更新:其他镜头实施策略

同构透镜

还有另外两种镜头编码值得考虑.第一个提供了一种很好的理论方法,可以将镜头视为将结构分解为场值和其他一切"的一种方式.

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'.

给定同构的类型

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

使得有效成员满足 hither .yon = idyon .这里 = id

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 镜片

我们可以对镜头进行建模,使它们可以由 (.)id 组成,即使没有 Category 实例,也可以使用>

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

作为我们镜头的类型.

然后定义一个镜头就像:

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.

我最近写了一篇关于如何进一步概括 van Laarhoven 镜头的文章得到可以改变字段类型的镜头系列,只需将此签名推广到

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

这确实有一个不幸的后果,即谈论镜头的最佳方式是使用 2 级多态性,但在定义镜头时不需要直接使用该签名.

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.

我在上面为 _2 定义的 Lens 实际上是一个 LensFamily.

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

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

我编写了一个库,其中包括镜头、镜头系列和其他概括,包括 getter、setter、折叠和遍历.它在 hackage 上以 lens 包的形式提供.

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.

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

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.

由于您不需要实际使用该包来定义新镜头,因此我之前担心保留 Haskell 98 库的问题减轻了很多压力.

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天全站免登陆