Answers:
我知道至少有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。
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
这种方法是首先注意到和罗素·奥康纳的描述Functor
是Lens
为Applicative
是Biplate
:引入多板,并在博客根据预印本由杰里米·吉本斯。
它还包括许多用于严格处理镜片的组合器和一些用于容器的备用镜片,例如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的担忧。
:->
data-accessor
,然后将其传递给Henning并停止关注。该a -> r -> (a,r)
表示形式也使我感到不舒服,并且我的原始实现就像您的Lens
类型一样。Heeennnninngg!
lens
软件包具有最丰富的功能和文档,因此,如果您不介意它的复杂性和依赖性,那么这就是解决方法。