镜头,fclabels,数据访问器-哪个结构访问和突变库更好


173

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

我个人是从数据访问器开始的,现在正在使用它们。但是,最近在Haskell咖啡馆上,有人认为fclabels更好。

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


3
到目前为止,lens软件包具有最丰富的功能和文档,因此,如果您不介意它的复杂性和依赖性,那么这就是解决方法。
模块化的

Answers:


200

我知道至少有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实例,(:->)该实例非常有用,因为它允许您组成镜头。它还提供了一种无法合法的Point类型,可以概括此处使用的镜头的概念,并提供一些用于处理同构的管道。

采用该规范的一个障碍fclabels是主程序包包含template-haskell管道,因此该程序包不是Haskell 98,它也需要TypeOperators扩展(完全没有争议)。

数据存取器

[编辑:data-accessor不再使用此表示形式,但已移至类似于的形式data-lens。不过,我保留了此评论。]

数据访问器比之流行一些fclabels,部分是因为它 Haskell98。但是,其内部表示形式的选择使我有点不满意。

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

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

因此,为了get获得镜头的值,您必须为'a'参数提交未定义的值!这使我感到震惊,因为它是一个非常丑陋且临时的实现。

就是说,Henning在单独的“ data-accessor-template ”包中包括了template-haskell管道,可以自动为您生成访问

它具有大量已使用的软件包的好处,即Haskell 98,并提供了非常重要的Category实例,因此,如果您不注意香肠的制作方法,则此软件包实际上是非常合理的选择。

镜片

接下来,有一个透镜包装,观察到透镜可以通过直接透镜定义单态同态来提供两个状态单态之间的状态单态同态。

如果它真的不愿意为镜头提供某种类型,那么它们将具有等级2的类型,例如:

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

结果,我宁愿不喜欢这种方法,因为它不必要地使您脱离Haskell 98(如果您希望抽象提供给镜头的类型)并剥夺了您Category获得镜头的实例的权限,那会让您与他们组成.。该实现还需要多参数类型类。

请注意,这里提到的所有其他镜头库都提供了一些组合器,或者可用于提供相同的状态聚焦效果,因此,直接以这种方式编码镜头不会获得任何好处。

此外,开头所述的附带条件在这种形式中并没有真正好的表达方式。与“ fclabels”一样,它确实提供了模板-haskell方法,可直接在主程序包中自动为记录类型生成镜头。

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

数据镜头

[编辑:从1.8.0版开始,它们已从comonad-transformers程序包移至data-lens。

我的data-lens包装中的商品以商店为准。

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个定律的“镜片”值子集正是那些包装功能为“ comonad coalgebra”的镜片。这将镜头的3个毛发定律转换l为2个无点等效值:

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

这种方法是首先注意到和罗素·奥康纳的描述FunctorLensApplicativeBiplate:引入多板,并在博客根据预印本由杰里米·吉本斯。

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

因此,data-lens形式为a 的镜片Category(与lenses包装不同),具有Haskell 98(不同于fclabels/ lenses),理智(与的后端不同data-accessor),并提供了更为有效的实施方式,data-lens-fd为愿意出门的人提供了与MonadState一起工作的功能可以使用Haskell 98的版本进行操作,而可通过访问data-lens-template

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

同构镜片

还有其他两个镜头编码值得考虑。第一种方法提供了一种很好的理论方法,可以将镜头视为将结构分解为视野和“其他一切”价值的一种方法。

给定同构类型

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

使有效成员满足hither . yon = id,并且yon . hither = id

我们可以用以下镜头代表镜头:

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

这些主要用作思考镜头含义的一种方式,我们可以将它们用作解释其他镜头的推理工具。

范·拉霍芬镜片

我们可以对镜头建模,使得它们可以由(.)和组成id,即使没有Category实例也可以通过使用

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

作为我们镜头的类型。

然后定义一个镜头就像:

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

您可以自己验证功能组成就是镜头组成。

我最近写了一篇文章,关于如何进一步泛化范拉尔霍芬(van Laarhoven)镜头,从而获得可以改变视野类型的镜头系列,只需将这个签名泛化为

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

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

Lens上面为定义的I _2实际上是一个LensFamily

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

我已经编写了一个库,其中包含镜头,镜头系列以及其他通用性,包括吸气剂,坐便器,折痕和遍历。它作为lens软件包提供在hackage上 。

同样,此方法的一大优势是,库维护人员实际上可以在库中以这种样式创建镜头,而无需为镜头库提供任何依赖,只需Functor f => (b -> f b) -> a -> f a为其类型'a'和'b' 提供类型的函数即可。这大大降低了采用成本。

由于您不需要实际使用该包来定义新镜头,因此减轻了我以前对保留库Haskell 98的担忧。


28
我喜欢fclabels的乐观方法:->
-Tener

3
HVR

10
与Haskell 1998兼容是否重要?因为它使编译器开发更容易?我们不应该改为谈论Haskell 2010吗?
yairchu 2011年

55
不好了!我是的原始作者data-accessor,然后将其传递给Henning并停止关注。该a -> r -> (a,r)表示形式也使我感到不舒服,并且我的原始实现就像您的Lens类型一样。Heeennnninngg!
luqui 2011年

5
Yairchu:主要是这样,因此您的库可能有机会使用ghc以外的编译器。没有其他人拥有模板Haskell。2010在此处未添加任何相关内容。
Edward KMETT 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.