Lenses、fclabels、data-accessor - 哪个用于结构访问和突变的库更好
至少有三个流行的库用于访问和操作记录字段。我所知道的有:data-accessor、fclabels 和 Lenses。
就我个人而言,我从数据访问器开始,现在正在使用它们。然而最近在 haskell-cafe 上有一种观点认为 fclabels 更优越。
因此我对这三个(也许更多)库的比较感兴趣。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
据我所知,至少有 4 个图书馆提供镜头。
透镜的概念是,它提供了与
提供两个功能同构的东西:一个 getter 和一个 setter,
遵循三个定律:
第一,如果你放了一些东西,你可以把它拿回来;
第二,获取然后设置并不重要。改变答案
第三,两次投入与一次投入相同,或者更确切地说,第二次投入获胜。
请注意,类型系统不足以为您检查这些定律,因此无论您使用哪种镜头实现,您都需要自己确保它们。
许多这些库还在顶部提供了一堆额外的组合器,通常还提供某种形式的模板 haskell 机制来自动生成简单记录类型字段的镜头。
考虑到这一点,我们可以转向不同的实现:
实现
fclabels
fclabels 可能是最容易推理的镜头库,因为它的
a :->; b
可以直接翻译为上面的类型。它提供了 类别(:->)
的 实例非常有用,因为它允许您组合镜头。它还提供了一个无法无天的Point
类型,它概括了这里使用的透镜的概念,以及一些处理同构的管道。采用 fclabels 的一个障碍是主包包含 template-haskell 管道,因此该包不是 Haskell 98,并且它还需要(相当无争议的)TypeOperators< /代码> 扩展名。
data-accessor
[编辑:
data-accessor
不再使用此表示形式,但已转移到与data-lens
类似的形式。不过,我保留了这个评论。]data-accessor 比
fclabels
,部分原因是它是 Haskell 98。然而,它对内部表示的选择让我有点呕吐。它用来表示镜头的类型
T
在内部定义为:因此,为了
获取
镜头的值,您必须为“a”提交一个未定义的值争论!在我看来,这是一个极其丑陋且临时的实现。也就是说,Henning 已经包含了 template-haskell 管道,可以在单独的 '数据访问器模板' 包。
它的好处是已经有大量的软件包使用它,比如 Haskell 98,并提供了最重要的
Category
实例,所以如果你不注意香肠是如何制作的,这个套餐实际上是相当合理的选择。镜头
接下来,有lens 包,它观察到,通过直接将lens定义为这样的单子同态,lens可以在两个状态单子之间提供状态单子同态。
如果它真的费心为其镜头提供一种类型,他们会有一个 2 级类型,例如:
结果,我宁愿不喜欢这种方法,因为它不必要地将您从 Haskell 98 中拉出来(如果您想要一种类型)抽象地提供给您的镜头)并剥夺您镜头的
Category
实例,这将允许您使用.
组合它们。该实现还需要多参数类型类。请注意,此处提到的所有其他镜头库都提供一些组合器,或者可用于提供相同的状态聚焦效果,因此直接以这种方式编码镜头不会获得任何效果。
此外,在这种形式中,开头所述的附加条件实际上并没有很好的表达。与“fclabels”一样,这确实提供了模板哈斯克尔方法,用于直接在主包中自动生成记录类型的镜头。
由于缺少
Category
实例、巴洛克编码以及主包中 template-haskell 的要求,这是我最不喜欢的实现。data-lens
[编辑:从 1.8.0 开始,这些已从 comonad-transformers 包转移到 data-lens]
我的
data-lens
包根据 存储 comonad。其中
Expanded this 相当于
您可以将其视为从 getter 和 setter 中分解出公共参数,以返回一个由检索元素的结果组成的对,以及一个用于放回新值的 setter。这提供了计算这样做的好处是,这里的“setter”可以回收一些用于获取值的工作,从而实现比 fclabels 定义更有效的“修改”操作,尤其是当访问器链接时。
这种表示法还有一个很好的理论依据,因为满足此响应开头所述的 3 个定律的“Lens”值的子集正是那些包装函数是存储 comonad 的“comonad 余代数”的透镜。这将镜头
l
的 3 个毛茸茸的定律转换为 2 个完美的无点等价物:这种方法首先在 Russell O'Connor 的
Functor
之于Lens
正如Applicative
之于Biplate
:介绍 Multiplate< /a> 并且是 基于杰里米·吉本斯 (Jeremy Gibbons) 的预印本撰写的博客。它还包括许多用于严格使用镜头的组合器和一些用于容器的库存镜头,例如
Data.Map
。因此,
data-lens
中的镜头形成了一个Category
(与lenses
包不同),是 Haskell 98(与fclabels
不同) >/lenses
),是理智的(与data-accessor
的后端不同),并提供稍微更高效的实现,data-lens-fd
为那些愿意走出 Haskell 的人提供使用 MonadState 的功能98,并且 template-haskell 机制现在可以通过data-lens-template 获得
。2012 年 6 月 28 日更新:其他镜头实现策略
同构镜头
还有另外两种镜头编码值得考虑。第一个给出了一种很好的理论方法,将镜头视为将结构分解为领域价值和“其他一切”的方法。
给定一个同构类型
,使得有效成员满足
hither 。 yon = id
和 yon 。 hither = id我们可以用以下方式表示镜头:
这些主要用作思考镜头含义的方式,我们可以使用它们作为推理工具来解释其他镜头。
van Laarhoven 镜头
我们可以对镜头进行建模,使得它们可以由
(.)
和id
组成,即使没有Category 实例,用作
我们镜头的类型。
那么定义一个透镜就像:
并且您可以自己验证函数组合就是透镜组合。
我最近写了一篇关于如何进一步推广 van Laarhoven 镜头以获得镜头的文章可以更改字段类型的系列,只需将此签名概括为
这确实会产生不幸的后果,即讨论镜头的最佳方式是使用 2 级多态性,但在定义镜头时不需要直接使用该签名。
我上面为
_2
定义的Lens
实际上是一个LensFamily
。我编写了一个库,其中包括镜头、镜头系列和其他概括,包括 getter、setter、折叠和遍历。它可以在 hackage 上以
lens
包的形式提供。同样,这种方法的一大优点是,库维护人员实际上可以在库中创建这种风格的镜头,而不会产生任何镜头库依赖性,只需提供类型为 Functor f => 的函数即可。 (b→fb)→一个-> f a,用于其特定类型“a”和“b”。这大大降低了采用成本。
由于您不需要实际使用该包来定义新镜头,因此我之前担心保留 Haskell 98 库的担忧减轻了很多。
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
providing two functions: a getter, and a setter
subject to three laws:
First, that if you put something, you can get it back out
Second that getting and then setting doesn't change the answer
And third, putting twice is the same as putting once, or rather, that the second put wins.
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 lawlessPoint
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 ofdata-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 asConsequently, 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:
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.where
Expanded this is equivalent to
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:This approach was first noted and described in Russell O'Connor's
Functor
is toLens
asApplicative
is toBiplate
: 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 aCategory
(unlike thelenses
package), are Haskell 98 (unlikefclabels
/lenses
), are sane (unlike the back end ofdata-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 viadata-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
such that valid members satisfy
hither . yon = id
, andyon . hither = id
We can represent a lens with:
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
(.)
andid
, even without aCategory
instance by usingas the type for our lenses.
Then defining a lens is as easy as:
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
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 aLensFamily
.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.