镜头水平构图

Horizontal composition of Lenses

箭头有两种构成,垂直:

(.) :: Arrow cat => cat b c -> cat a b -> cat a c

水平

(***) :: Arrow cat => cat a b -> cat a' b' -> cat (a,a') (b,b')

(对于任何滥用术语的行为,我向任何范畴理论家道歉)

镜头也可以垂直构图:

(.) :: Lens s t q r -> Lens q r a b -> Lens s t a b

他们能横向排版吗?


使用 van Laarhoven 透镜和混凝土透镜的正常定义:

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

data ALens s t a b = ALens
  { get :: s -> a
  , set :: b -> s -> t
  }
type ALens' s a = ALens s s a a

fromALens :: ALens s t a b -> Lens s t a b
toALens :: Lens s t a b -> ALens s t a b

我想出了如何水平组合简单的具体镜头:

along :: ALens' s a -> ALens' s b -> ALens' s (a,b)
along l0 l1 = ALens
  { get = \s -> (get l0 s, get l1 s)
  , set = \(a,c) -> set l1 c . set l0 a
  }

以及如何使用该定义水平构成简单的 van Laarhoven 镜头:

zip :: Lens' s a -> Lens' s b -> Lens' s (a,b)
zip l0 l1 = fromALens (toALens l0 `along` toALens l1)

在不改变实现的情况下,我什至可以放宽类型签名 alongzip 允许其中一个透镜是多态的:

along :: ALens' s a -> ALens s t b c -> ALens s t (a,b) (a,c)
zip :: Lens' s a -> Lens s t b c -> Lens s t (a,b) (a,c)

经过一些手动等式推理,我成功地实现了 zip 转换为具体镜片并返回:

zip l0 l1 h s = h (s ^. l0, s ^. l1) <&> \(a,c) -> s & (l0 .~ a) & (l1 .~ c)

(虽然我当然不相信这是最优雅或最守法的实施)

我还在想是否有一个等价的水平 非简单镜片的成分:

(***) :: Lens r s a b -> Lens s t c d -> Lens r t (a,a') (b,b')

镜头不能有适当的水平构图。当水平组合合法的镜头时,所有的镜头法则都是微不足道的。

只要镜头重叠,您的 zip 就会崩溃。 10 & zip id id .~ (1, 2)的结果是什么?当镜头目标彼此完全不相交时,您所拥有的将会起作用,所以这没有错。这不是一般的水平构图。输入值还有其他限制。

当您想组合非简单镜头时,情况会变得更糟。现在你也有一个类型问题,即使镜头是不相交的。给定不相交的镜头 lens1 :: Lens s1 t1 a1 b1lens2 :: Lens s2 t2 a2 b2,它们的水平构图类型是什么? Lens s t (a1, a2) (b1, b2)当然是一些st的最终目标。从镜头的工作方式,我们知道 (s ~ s1, s ~ s2),所以这不是问题。但是 t 是什么?没有办法计算它。您可能需要按照 "apply the changes made to the type by each" 的方式进行某种操作,但定义不明确。最好的情况是,您可以创建一个类型族来覆盖 st1t2 的特定组合。