镜头,fclabels,数据访问器 – 哪个结构访问和变异的库更好

至less有三个stream行的库访问和操作logging的领域。 我所知道的是:数据访问器,fclabels和镜头。

我个人从数据访问开始,现在正在使用它们。 然而最近在haskell咖啡馆有一个fclabels优越的意见。

所以我对这三个(也许更多)图书馆的比较感兴趣。

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

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

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

提供两个function:一个吸气剂和一个吸气剂

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

受三个法律的约束:

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

 get l (put lba) = b 

其次,获取并设置不会改变答案

 put l (get la) a = a 

第三,两次放一次,或者说,第二次放胜。

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

请注意,types系统不足以为您检查这些法律,所以无论您使用什么镜头实施,都需要自行确保。

这些库中的许多库还在顶部提供了一堆额外的组合器,并且通常使用某种forms的模板haskell机制来为简单loggingtypes的字段自动生成镜头。

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

实现

fclabels

fclabels也许是最容易推断的镜头库,因为它的a :-> b可以直接转换为上面的types。 它为(:->)提供了一个Category实例,这是非常有用的,因为它可以让你编写镜头。 它也提供了一个无法无天的Pointtypes,它概括了这里使用的透镜的概念,以及一些处理同构的pipe道。

阻碍采用fclabels一个障碍是主包包含了模板fclabels (haskell)pipe道,所以包不是Haskell 98,而且它还需要(非常有争议的) TypeOperators扩展。

数据访问

[编辑: data-accessor不再使用这种表示,但已经移动到类似于data-lens 。 虽然我保留这个评论。]

data-accessor比fclabels更受欢迎,部分原因是它 Haskell 98.但是,它select的内部表示方式让我有些fclabels

T用来表示镜头的types在内部定义为

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

因此,为了get镜头的价值,您必须为“a”参数提交一个未定义的值! 这令我感到难以置信的丑陋和特别的实施。

也就是说,Henning已经包含了模板 – haskellpipe道,在一个单独的' data-accessor-template '包中为你自动生成访问器 。

它有一个体积很大的包已经使用它的好处,作为Haskell 98,并提供最重要的Category实例,所以如果你不注意如何香肠制作,这个包实际上是非常合理的select。

镜头

接下来是透镜包装,它观察到透镜可以通过直接透镜定义这样的单子同态而在两个状态单子之间提供状态单子同态。

如果真的很想提供一个types的镜头,他们将有一个排名2types,如:

 newtype Lens st = Lens (forall a. State ta -> State sa) 

因此,我宁愿不喜欢这种方法,因为它不必要地将你从Haskell 98中抽出来(如果你想在抽象中提供一种types的镜头),并且剥夺你镜头的Category实例,你和他们合作. 。 该实现还需要多参数types的类。

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

而且,在开始时所说的附带条件在这种forms上并不是很好的expression。 与'fclabels'一样,这也提供了在主包中直接为loggingtypes自动生成镜头的template-haskell方法。

由于缺lessCategory实例,巴洛克编码以及主包中的模板haskell的要求,这是我最不喜欢的实现。

数据透镜

[编辑:从1.8.0,这些已经从共同变压器包到数据镜头]

我的data-lens包提供了商店 comonad方面的镜头。

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

哪里

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

展开这相当于

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

你可以把它看作是从getter和setter中找出一个共同的参数,返回一个包含检索元素结果的对和一个返回新值的setter。这提供了计算的好处,即“setter”这里可以回收一些用于获取值的工作,比fclabels定义更有效的“修改”操作,特别是当访问器被链接时。

对于这种表示,也有一个很好的理论上的理由,因为满足这个响应开头所述的3个定律的“透镜”值的子集正好是那些包装函数是商店共同作用的“共同代数”的那些透镜。 这将镜头的3个毛发法则转换为2个非常好的无点对等物:

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

这种方法首先被注意到,并在Russell O'Connor的Functor is to Lens中被描述为ApplicativeBiplate :介绍Multiplate ,并在 Jeremy Gibbons 的预印本的基础上进行了博客 。

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

因此, data-lens镜头中data-lens形成一个Category (与lenses包不同),Haskell 98(与fclabels / lenses不同)是理智的(与data-accessor的后端不同),并提供稍微更高效的实现, data-lens-fd为那些愿意走出Haskell 98的人提供了与MonadState一起工作的function,而模板 – haskell机器现在可以通过data-lens-template

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

同构镜片

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

给定一个同构types

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

使有效的成员满意hither . yon = id hither . yon = idyon . hither = id yon . hither = id

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

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

这些主要是用来思考镜头的意义的一种方法,我们可以用它们作为解释其他镜头的推理工具。

范拉赫霍芬透镜

我们可以模拟镜头,使它们可以用(.)id ,即使没有使用Category实例

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

作为我们的镜头的types。

然后定义一个镜头就像:

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

你可以自己validationfunction组成是镜头组成。

我最近写了如何进一步推广van Laarhoven镜头,以获得可以改变字段types的镜头系列,只需将此签名概括为

 type LensFamily abcd = forall f. Functor f => (c -> fd) -> a -> fb 

这确实有一个不幸的后果,那就是最好的方法是使用等级2的多态性,但是在定义镜头时你不需要直接使用这个签名。

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

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

我已经写了一个库,包括镜头,镜头系列,以及其他一些概括,包括getter,setters,折叠和遍历。 作为lens包,它可以在黑客上使用。

同样,这种方法的一大优点是,图书馆维护人员可以在你的图书馆中以这种风格实际创build镜头,而不会产生任何镜头库依赖,只要提供types为Functor f => (b -> fb) -> a -> fa他们的特殊types'a'和'b'。 这大大降低了采用的成本。

由于您不需要真正使用这个软件包来定义新的镜头,因此我之前担心的保留Haskell 98库的压力要承受很大的压力。