I'm diving continually deeper into Kmett's lenses; today I'm attempting to write some custom traversals, up until now I've managed to get along by composing existing traversals to create new ones, but I'm doing something a little more complicated and got stuck.
I'm writing a text editor and I'm just adding multiple cursors, originally each buffer had one cursor and had a lens to focus it, now I'm generalizing to a list of cursors and want a traversal over the list. The trick is that in the previous case my lens did some validation inside the setter to ensure that a cursor was clamped to fit within the valid range of the buffer's text. It looked like this:
clampCursor :: Text -> Cursor -> Cursor
cursor :: Lens' Buffer Cursor
cursor = lens getter setter
where getter buf = buf^.curs
setter buf new = let txt = buf^.text
in buf & curs .~ clampCursor txt new
Note how it uses the text info from the context of the buffer to create a lens over the cursor; (Also I'd love to hear about any cleaner ways to do this instead of making custom lenses if anyone has suggestions, I've found myself doing it a lot).
So now that I've got multiple cursors I need to transform this into a Traversal', but of course I can't define a traversal using the lens getter setter method; Looking around for how to define traversals I read this tutorial; Which states the following:
Question: How do I create traversals?
Answer: There are three main ways to create primitive traversals:
- traverse is a Traversal' that you get for any type that implements Traversable
- Every Lens' will also type-check as a Traversal'
- You can use Template Haskell to generate Traversal's using makePrisms since every Prism' is also a Traversal' (not covered in this tutorial)
None of those methods really help out here; I've also seen the style where you create a traversal using applicative style, but it's always been a bit confusing to me and I don't really know how I would use it in this case to get what I want.
I suppose I could write a Lens' Buffer [Cursor] which maps over the cursors in the setter to perform the validation and THEN traverse that list, but I figure there's got to be a way to bake it into the traversal AFTER the traverse (when each single element is focused) somehow. Maybe there's a better way to do this entirely;
Still looking to learn as much as I can about traversals, so any explanations are appreciated! Thanks!
EDIT: @dfeuer pointed out that when you do this sort of validation you end up with invalid lenses, I really like the clean interface it provides to do them inside the lens; and so far as I know, since the validation is idempotent it shouldn't cause any real issue, but I'm open to suggestions on how to do this better.
Lensis not quite a law-abiding lens. Can you give some more information about what aCursoris and how it's used? Your current setup feels a bit odd to me. I don't tend to think of a cursor as being "part of" a buffer. I suspect what you may really want is something more like a collection of lens-like "pointers" into a buffer. - dfeuerTextsegments, with cursors implicitly between them. It's quite possible to make a law-abidingSetterperform validation, because setters have almost no laws. Lenses are a different beast. - dfeuerCursor swill be known in-bounds for aBuffer s; you can create cursors usingPrisms. Thescan also reify the buffer size (seeData.Reflectionin thereflectionpackage). - dfeuer