The problem is pretty simple. I have a structure that looks something like this
data Foo = Foo [Bar]
data Bar = Boo | Moo Item Int
data Item = Item String Int
and I have a lens for changing the contents of the Item
s inside the data structure, such as this
let foos = [Foo [Boo, Moo (Item "bar" 20) 10]]
over (traverse._Foo._Moo._1._Item._1) ("foo" ++) foos
-- which yields [Foo [Boo, Moo (Item "foobar" 20) 10]]
The structure here isn't important, I just wanted to show an example that uses prisms and something deeply nested.
Now the problem is that I need the function passed to over
to be String -> IO String
, instead of just String -> String
. A similar thing to what I'm looking for here is something like mapM
, but with lenses. Is it possible to do something like this?
Lens provides the traverseOf
function, which is exactly like mapM
but takes a lens-like (it takes a traversal, which includes lenses and prims) over which you want to map
.
traverseOf :: Functor f => Iso s t a b -> (a -> f b) -> s -> f t
traverseOf :: Functor f => Lens s t a b -> (a -> f b) -> s -> f t
traverseOf :: Applicative f => Traversal s t a b -> (a -> f b) -> s -> f t
So for your example, you can just use:
traverseOf (traverse._Foo._Moo._1._Item._1) (... expression of type String -> IO String ...) foos
There is also an operator version of traverseOf
, called %%~
.
If you're a bit familar with the representation of lenses inside the lens library, you might notice that traverseOf = id
! So, with this knowledge, you can rewrite the example to just:
(traverse._Foo._Moo._1._Item._1) (... expression of type String -> IO String ...) foos
(You even used traverse
which is just mapM
to build the traversal! lenses / prims are just like traverse
, but more specific.)
But this is just an aside, you might nevertheless want to use traverseOf
for clarity.