数据结构 – 透镜,fclabels,数据访问器 – 用于结构访问和突变的库更好

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

我个人我开始与data-accessor,我现在使用它们。然而最近在haskell咖啡馆有一个fclabels的意见是优越的。

因此,我有兴趣比较这三个(也许更多)图书馆。

最佳答案
至少有4个图书馆我知道提供镜头。

镜头的概念是它提供同构的东西

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

提供两个函数:一个getter和一个setter

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

受三项法律约束:

首先,如果你放了东西,你可以得到它

get l (put l b a) = b 

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

put l (get l a) a = a

第三,放两次是和放一次,或者说,第二次赢了相同。

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

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

这些库中的许多还在顶部提供了一组额外的组合器,并且通常某些形式的模板haskell机械自动生成用于简单记录类型的字段的透镜。

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

实现

fclabels

fclabels也许是最容易推理的镜头库,因为它的a: – > b可以直接转换为上述类型。它为(: – >)提供了一个Category实例,这是有用的,因为它允许你组成镜头。它还提供了一个无法统计的点类型,概括了这里使用的镜头的概念,和一些管道处理同构。

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

数据访问器

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

其用于表示透镜的类型T在内部定义为

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

因此,为了获得镜头的值,您必须为’a’参数提交未定义的值!这使我作为一个令人难以置信的丑陋和临时实施。

也就是说,Henning已经包含了template-haskell管道自动生成访问器在一个单独的“data-accessor-template”包。

它有一个很大的一套已经使用它的包,Haskell 98,并提供了所有重要的类实例的好处,所以如果你不注意如何制作香肠,这个包实际上是相当合理的选择。

镜头

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

如果它实际上打算提供一个类型的镜头,他们会有一个2级类型:

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

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

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

此外,开始时声明的附带条件在这种形式下并不真的有一个很好的表达。与’fclabels’一样,这提供了template-haskell方法,用于在主包中直接自动生成记录类型的镜头。

因为缺少Category实例,巴洛克编码,以及主包中template-haskell的要求,这是我最不喜欢的实现。

数据透镜

[编辑:从1.8.0,这些已经从comonad-transformers包移动到数据透镜]

我的data-lens包提供镜片的Store共聚单体。

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定义更有效的“修改”操作,特别是当访问器链接时。

对于该表示还有一个很好的理论上的理由,因为满足在该响应开始时所述的3个规律的’Lens’值的子集正好是那些透镜,其包装函数是用于商店公共的“共同的代数” 。这转换了3毛发定律镜头l下降到2很好pointfree等价物:

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

这种方法首先在Russell O’Connor的Functor is to Lens as Applicative is to Biplate: Introducing Multiplate中被注释和描述,并且是Jeremy Gibbons的blogged about based on a preprint

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

因此,数据透镜中的透镜形成一个类别(不像透镜包),是Haskell 98(不像fclabels /透镜),是理智的(不同于数据访问器的后端),并提供稍微更有效的实现,data-lens-fd提供用于与MonadState一起工作的人员,他们愿意在Haskell 98之外工作,而template-haskell机制现在可以通过data-lens-template

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

同构镜片

还有两个其他的透镜编码值得考虑。第一个给出了一个很好的理论方法来查看镜头作为一种方式来打破结构的字段的价值,和“一切”。

给定同构的类型

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

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

我们可以代表镜头:

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

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

van Laarhoven镜片

我们可以模拟镜头,使它们可以用(。)和id组成,即使没有类别实例通过使用

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

作为我们的镜头的类型。

然后定义镜头就像:

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

并且您可以自己验证功能组合是镜头组成。

我最近写了如何进一步generalize van Laarhoven lenses得到镜头家族,可以改变字段的类型,只是通过将这种签名

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

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

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

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

我写了一个库,包括镜头,透镜族和其他概括,包括getter,setter,folds和遍历。它可作为lens包装在hackage上。

同样,这种方法的一个大优点是,库维护者实际上可以在您的库中创建这种风格的镜头,而不需要任何镜头库依赖,只需提供类型Functor f => (b – > f b) – > a – > f a,对于它们的特定类型’a’和’b’。这大大降低了采用的成本。

因为你不需要真正使用包来定义新的镜头,它需要很大的压力,我以前关心的保持库Haskell 98。

转载注明原文:数据结构 – 透镜,fclabels,数据访问器 – 用于结构访问和突变的库更好 - 代码日志