Search code examples
haskelllenseshaskell-lens

Combining lenses


Using a lens library I can apply a modification function to individual targets, like so:

Prelude Control.Lens> (1, 'a', 2) & _1 %~ (*3)
(3,'a',2)
Prelude Control.Lens> (1, 'a', 2) & _3 %~ (*3)
(1,'a',6)

How can I combine those individual lenses (_1 and _3) to be able to perform this update to both of the targets at once? I expect something in the spirit of the following:

Prelude Control.Lens> (1, 'a', 2) & ??? %~ (*3)
(3,'a',6)

Solution

  • Using untainted from the Settable type class in Control.Lens.Internal.Setter, it is possible to combine two setters, but the result will also only be a setter and not a getter.

    import Control.Lens.Internal.Setter
    
    -- (&&&) is already taken by Control.Arrow
    (~&~) :: (Settable f) => (c -> d -> f a) -> (c -> a -> t) -> c -> d -> t
    (~&~) a b f = b f . untainted . a f
    

    You can test this:

    >>> import Control.Lens
    >>> (1, 'a', 2) & (_1 ~&~ _3) %~ (*3)
    (3,'a',6)
    

    EDIT

    You don't actually need to use internal functions. You can use the fact that Mutator is a monad:

    {-# LANGUAGE NoMonomorphismRestriction #-}
    
    import Control.Monad
    import Control.Applicative
    
    (~&~) = liftA2 (>=>)
    
    -- This works too, and is maybe easier to understand: 
    (~&~) a b f x = a f x >>= b f