如何“并联”组合镜头

8

我是 Control.Lens 的新手,正在尝试将两个镜头 "并行"(而不是按顺序)组合在一起,就像使用 `Control.Arrow.&&&` 一样。

如果我采用 lens 文档中的示例:

`data Foo a = Foo { _baz :: Int, _bar :: Int, a }

我希望能够做以下的事情:

>> let foo = (bar &&& baz) .~ (1, 20) $ Foo undefined undefined "FOO"
>> foo ^. (bar &&& baz) 
(1, 20)

我已经到处寻找,但无法找到一种方法来这样做。是因为:
  • (&&&)存在其他名称,并且我错过了它。
  • 它没有用。我不应该需要它,因此没有人去实现它。
  • 可以用另一种方式轻松完成(使用both或<*>)

更新

&&&可以这样实现:
(/|\) :: Lens' f a -> Lens' f b -> Lens' f (a, b)
a /|\ b = lens getBoth setBoth where
    getBoth f = (f ^. a, f ^. b)
    setBoth f (v, w) = a .~ v $ f' where
        f' = b .~ w $ f

barz :: Lens' Foo (Int, Int)
barz = bar /|\ baz

然而,它需要一个类型签名,这有点烦人。
3个回答

7

这个组合器可能无法实现。考虑一下:

> (baz &&& baz) .~ (1,5)

这应该做什么?

即使是较弱的组合形式:

(/|\) :: Lens' s a -> Lens' s a -> Traversal' s a
a /|\ b = (a &&& b) . both

会违反规律:

例如,让我们看看 baz /|\ baz。由于 Traversal 也是一个 Setter,它还必须满足 Setter 规律。现在,考虑第二个 Setter 规律:

over (baz /|\ baz) (f . g) = over (baz /|\ baz) f . over (baz /|\ baz) g

现在,我们得到:
over (baz /|\ baz) (f . g) 
= \(Foo _baz _bar) -> Foo (f . g . f . g $ _baz) _bar

并且:

over (baz /|\ baz) f . over (baz /|\ baz) g
= \(Foo _baz _bar) -> Foo (f . f . g . g $ _baz) _bar

这两个显然不同。问题在于当这两个镜头“重叠”时,类型无法编码。


小错误:在第二个 Setter 中,l 应该是 f。(顺便说一句,“编辑必须至少为6个字符”真的很烦人) - chi
5
@bennofs说:“是的,a &&& a 违反了 lens 定律,但这有什么关系?重要的是 bar &&& baz 没有违反,这才是最重要的。我可以找到参数(a,b),使得lens a b 违反 lens 定律。但这并不能阻止 lens 函数成为有效的函数。此外,&&& 可以被实现,并且似乎可行(请参见更新部分)。 - mb14

3
我刚刚发现了这个实际函数,它在 lens 中被定义为 lensProduct(请参见 Control.Lens.Unsound 模块中的有关不安全性的注释)。请点击此处查看lens
-- | A lens product. There is no law-abiding way to do this in general.
-- Result is only a valid 'Lens' if the input lenses project disjoint parts of 
-- the structure @s@. Otherwise "you get what you put in" law
--
-- @
-- 'Control.Lens.Getter.view' l ('Control.Lens.Setter.set' l v s) ≡ v
-- @
--
-- is violated by
--
-- >>> let badLens :: Lens' (Int, Char) (Int, Int); badLens = lensProduct _1 _1
-- >>> view badLens (set badLens (1,2) (3,'x'))
-- (2,2)
--
-- but we should get @(1,2)@.
--
-- Are you looking for 'Control.Lens.Lens.alongside'?
--
lensProduct :: ALens' s a -> ALens' s b -> Lens' s (a, b)
lensProduct l1 l2 f s =
    f (s ^# l1, s ^# l2) <&> \(a, b) -> s & l1 #~ a & l2 #~ b

谢谢。我确实没有时间看它,但是“alonside”是什么? - mb14
alongside 将两个不同结构上的镜头组合成一个元组结构上的镜头,类似于 ***,就像 lensProduct 被视为类似于 &&& - leftaroundabout

1
作为对另一个问题的答案,我提出一个比lens库更通用的。除了其他功能外,引用可以“并行”组合。
当然,这可能会引起一些有趣的问题。例如twice = self &+& self是一个访问自身两次的引用:
 a ^* twice == [a,a]
 (twice *= x) a == x
 (twice *- f) a == f (f a)

不要被不同的运算符所困扰。^*类似于^..*=类似于.~,而*-类似于Control.Lens.Operators中的%~


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接