Search code examples
haskellhaskell-lens

Saving a position with Lenses


I'm trying to use xml-conduit and xml-lens for parsing and traversing an XML document. Instead of having to traverse the same parts of the document multiple times, I would like to store the traversal up until the previous point and then drill down further.

ex.

let pos = doc ^. root . el "foo"
    bar = pos . text
    baz = pos ./ el "quux" . text

When I try to do this, I get the following error:

No instance for (Data.Monoid.Monoid Element)
  arising from a use of `el'
Possible fix:
  add an instance declaration for (Data.Monoid.Monoid Element)
In the second argument of `(.)', namely `el "foo"'
In the second argument of `(^.)', namely `root . el "foo"'
In the expression: doc ^. root . el "foo"

What can I do to store this intermediate position?


Solution

  • The type error happens because el is a Traversal. Traversals point to multiple values (in this case, all foo elements). (^.), on the other hand, always returns a single value. Using (^.) with a Traversal only works if the target type is a Monoid; in that case, the multiple values are smashed into a single one using mappend. Element, however, is not a Monoid (because there is no sensible way to combine XML elements into a single element), and so (^.) does not work. To get the matched XML elements, you should use (^..) instead, which will return them as a list.

    As for the question proper ("What can I do to store this intermediate position?"), the Traversal is a reference to the position, so if you want to compose it further you don't want to "dereference" it with (^.) or (^..). The following should work:

    let pos = root . el "foo"
        baz = pos ./ el "quux" . text
        elemTexts = doc ^.. baz