Suppose we have a list of lenses [Lens' (S a) a]
on a data structure S a
. I want to modify the focus of each lens in the data structure in the same way.
I could do this like so:
s :: S a
s = _
ls :: [Lens' (S a) a]
ls = [a, b, c]
a, b, c = _
f :: a -> a
f = _
s' :: S a
s' = s
& a %~ f
& b %~ f
& c %~ f
That's OK, but what if I have 10, 100 lenses? I would like to have something like
s' :: S a
s' = s & ls ??? f
(???) :: *
where I cannot find the operator (???)
.
Maybe it is also possible to convert ls
to a traversal and simply use (%~)
, I
don't know.
Do you have an idea?
CodePudding user response:
The first problem is that you can't actually define the list ls
as you've done in your question: that type is illegal. As discussed in that question, to put a lens into a container you need to reify the lens. Once you've done that, you turn each lens into a function by applying (%~ f)
, and then it's a simple fold to compose those functions.
What you get out is not a lens or a traversal, but merely a function of type S a -> S a
(the same as you get from a %~ f
). I found a discussion of why it's not generally possible to combine multiple lenses, setters, or traversals into a single traversal in the way you mentioned you might hope for.
Here's an implementation of the ideas I outlined above.
data S a = S {_x, _y :: a} deriving Show
makeLenses ''S
ls :: [ReifiedLens' (S (S Int)) Int]
ls = [Lens (x.x), Lens (y.y)]
overEach :: [ReifiedLens' s a] -> (a -> a) -> (s -> s)
overEach ls f s = foldr applySetter s ls
where applySetter (Lens l) acc = acc & l %~ f
s :: S (S Int)
s = S (S 1 2) (S 3 4)
λ> s & overEach succ ls
S {_x = S {_x = 2, _y = 2}, _y = S {_x = 3, _y = 5}}
CodePudding user response:
(???) :: [ReifiedLens' (s a) a] -> (a -> a) -> s a -> s a
reLenses ??? f = foldl (.) identity $ map ((%~ f) . runLens) reLenses
Wrap your lenses in the Lens
constructor to get a ReifiedLens'
.