2

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?

Dominik Schrempf
  • 827
  • 8
  • 14
  • There's a discussion below of why lenses can't be combined into lenses and why traversals can't be combined into traversals. But I think also lenses can't be combined into traversals. Consider combining the two obvious lenses between `(a, b, b)` and `(a, b)`: to make a traversal that behaves like your `&`/`%~` chain, the change to the second `(a, b)` would need to observe the `a` output by the first change. That's exactly the kind of observation that cannot be done by `Applicative`. – Daniel Wagner Nov 12 '21 at 15:34

2 Answers2

1

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}}
amalloy
  • 89,153
  • 8
  • 140
  • 205
  • Isn't it fine to make lists of lenses now that GHC finally supports impredicativity? – Joseph Sible-Reinstate Monica Nov 12 '21 at 19:06
  • Thanks for your explanation and the link. I didn't know this was so complicated. Luckily, I create the lenses from a list of variables (let's call them indices). So now, I just fold over the list of indices, similar to your `overEach`. Then I avoid reified lenses :). – Dominik Schrempf Nov 12 '21 at 19:36
  • @JosephSible-ReinstateMonica Perhaps. When I tried OP's code, GHC didn't suggest a language extension to turn on, it just told me my type was illegal. So I didn't look very hard to see if I'm missing some recent development. I see I am still on GHC 8.x, while impredicativity is for 9.x+. – amalloy Nov 12 '21 at 19:53
0
(???) :: [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'.

John
  • 14,944
  • 12
  • 57
  • 57