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)
share|improve this question
    
I'm not sure that there is a sensible implementation of such an operation. Say that the operator that combines your two lenses is (&&&). It must have a type something like (&&&) :: Lens a b -> Lens a b -> Lens a b so that you can use it in the same way as the two lenses that you combine to make it. Given that view _1 (1,2) = 1 and view _2 (1,2) = 2, what do you expect the result of view (_1 &&& _2) (1,2) to be? – Chris Taylor Jul 8 '13 at 14:26
    
@ChrisTaylor I don't really need the "getter" functionality. Although AFAIU in this library it is conventional to approach such cases with monoid, e.g. the Traversal. – Nikita Volkov Jul 8 '13 at 15:09
2  
I linked this in the comments below, but in case someone misses it, there's a lens issue about combining traversals and the trouble with it. – shachaf Jul 9 '13 at 0:11
    
You cannot combine lenes, or setters safely in such a way in general because there is no way to enforce that the two lens or setters do not overlap. – Russell O'Connor Jul 18 '13 at 12:42
up vote 17 down vote accepted

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
share|improve this answer
    
Thanks! Is there a way to maybe construct a Traversal? – Nikita Volkov Jul 8 '13 at 15:05
3  
No. It unfortunately doesn't really work in general. – shachaf Jul 9 '13 at 0:09
1  
(_1 ~&~ _1) does not satisfy the Setter (aka SEC) laws. – Russell O'Connor Jul 18 '13 at 12:33
1  
((_1 ~&~ _1) %~ ('y':)) . ((_1 ~&~ _1) %~ ('z':)) $ ("",()) === ("yyzz",()) while (_1 ~&~ _1) %~ (('y':) . ('z':)) $ ("",()) === ("yzyz",()) – Russell O'Connor Jul 18 '13 at 12:40
    
@RussellO'Connor So in other words, setter1 ~&~ setter2 is only a valid setter if setter1 and setter2 are "orthogonal"? – Dan Burton Jul 18 '13 at 20:58

There is a variation on what you are asking for which is more general:

(/\)
    :: (Functor f)
    => ((a -> (a, a)) -> (c -> (a, c)))
    -- ^ Lens' c a
    -> ((b -> (b, b)) -> (c -> (b, c)))
    -- ^ Lens' c b
    -> (((a, b) -> f (a, b)) -> (c -> f c))
    -- ^ Lens' c (a, b)
(lens1 /\ lens2) f c0 =
    let (a, _) = lens1 (\a_ -> (a_, a_)) c0
        (b, _) = lens2 (\b_ -> (b_, b_)) c0
        fab = f (a, b)
    in fmap (\(a, b) ->
            let (_, c1) = lens1 (\a_ -> (a_, a)) c0
                (_, c2) = lens2 (\b_ -> (b_, b)) c1
            in c2
            ) fab

infixl 7 /\

Just focus on the type signature with lens type synonyms:

Lens' c a -> Lens' c b -> Lens' c (a, b)

It takes two lenses and combines them into a lens to a pair of fields. This is slightly more general and works for combining lenses that point to fields of different types. However, then you'd have to mutate the two fields separately.

I just wanted to throw this solution out there in case people were looking for something like this.

share|improve this answer
1  
A pithier type, for those like them (/\) :: LensLike' ((,) a) c a -> LensLike' ((,) b) c b -> Lens' c (a, b). Also, the obvious restriction, (/|\) :: LensLike' ((,) a) c a -> LensLike' ((,) a) c a -> Traversal' c a with a /|\ b = (a /\ b) . both. Why aren't these in lens? – J. Abrahamson Jul 9 '13 at 4:51
    
Immediate answer: github.com/ekmett/lens/issues/315 – J. Abrahamson Jul 9 '13 at 17:44

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.