Search code examples
haskellhaskell-lenslenses

How can I use `over` from Control.Lens but perform a monadic action and collect the results?


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 Items 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?


Solution

  • 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.