Search code examples
haskellhaskell-lens

How to combine lenses in "parallel"


I'm new to the excelent Control.Lens and I'm trying to combine 2 lens in "parallel" (not in sequence) like I would do with `Control.Arrow.&&&).

If I take the example from the lens documentation:

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

I would like to be able to do stuff like :

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

I've looked everywhere and I could not find a way to do so. Is that because :

  • (&&&) exist with another name and I missed it.
  • It's useless. I should not need it, therefore nobody bothered implementing it.
  • It's trivial to do in another way (using both or <*>)

Update

&&& can be implemented that way :

(/|\) :: 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

However, it needs a type signature which is a bit annoying.


Solution

  • I've just stumbled over this actual function, as it is defined in lens (with a fat comment about the unsoundness right in the module name: Control.Lens.Unsound)

    -- | 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