Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a function to traverse the contents of a lens #471

Open
ocharles opened this issue Nov 24, 2022 · 6 comments
Open

Add a function to traverse the contents of a lens #471

ocharles opened this issue Nov 24, 2022 · 6 comments

Comments

@ocharles
Copy link

ocharles commented Nov 24, 2022

In lens, we have:

traverseOf :: LensLike f s t a b -> (a -> f b) -> s -> f t

When traverseOf is given a lens, we get

traverseOf :: Functor f => (a -> f b) -> s -> f t

The only corresponding function in optics is toLensVL. This has the same type and behavior, but the name is much less informative. I'd like to suggest optics gets a combinator that has a better name than toLensVL.

As an example of how I use this:

uninstallTool :: MonadThrow m => Finite numberOfSlots -> Toolbank numberOfSlots -> m (Tool, Toolbank numberOfSlots)
uninstallTool slotIndex (Toolbank toolbank) =
  getCompose $ Toolbank <$> traverseOf (V.ix slotIndex) (Compose . Slot.uninstallTool) toolbank

Here V.ix comes from Data.Vector.Sized. Slot.uninstallTool :: MonadThrow m => Slot -> m (Tool, Slot), so by using traverseOf with Compose, I essentially get the way to update a "toolbank slot", while also returning a Tool as a result.

Using toLensVL would make this much less obvious.

@adamgundry
Copy link
Member

Hmm, we have traverseOf already, in Optics.Traversal. Although it only requires a Traversal and thus correspondingly the functor must be Applicative, so it is not a drop in replacement, but it should work in your example?

I suppose we could have traverseOfLens or something as a synonym for toLensVL, but I'm not sure it is really worth adding another name.

At the very least we could explain this better in Optics.Lens, though.

@ocharles
Copy link
Author

Sorry, I should have said why traverseOf doesn't work. I'm traversing here with Compose ((,) Tool) m, and that is only an Applicative if we have Monoid Tool, which I don't have. This is because:

instance Monoid a => Applicative ((,) a)

This is why I'm looking for a lens-specific traversal, where I don't need as much as Applicative.

@adamgundry
Copy link
Member

I see, sorry for not reading your example closely enough to spot that it doesn't support Applicative.

We currently have (excluding indexed versions)

toIsoVL     :: Is k An_Iso => Optic k is s t a b -> IsoVL s t a b
toPrismVL   :: Is k A_Prism => Optic k is s t a b -> PrismVL s t a b
toLensVL    :: Is k A_Lens => Optic k is s t a b -> LensVL s t a b
traverseOf  :: (Is k A_Traversal, Applicative f) => Optic k is s t a b -> (a -> f b) -> s -> f t
atraverseOf :: (Is k An_AffineTraversal, Functor f) => Optic k is s t a b -> (forall r. r -> f r) -> (a -> f b) -> s -> f t
traverseOf_ :: (Is k A_Fold, Applicative f)  => Optic' k is s a  -> (a -> f r) -> s -> f ()

which feels a bit inconsistent.

So it is plausible to add (some name for)

traverseOf' :: (Is k A_Lens, Functor f) => Optic k is s t a b -> (a -> f b) -> (s -> f t)

and perhaps we might also add

toTraversalVL :: Is k A_Traversal => Optic k is s t a b -> TraversalVL s t a b
toAffineTraversalVL :: Is k An_AffineTraversal => Optic k is s t a b -> AffineTraversalVL s t a b
toFoldVL :: Is k A_Fold => Optic' k is s a -> FoldVL s a

(and indexed versions).

Of course these are somewhat redundant, but the fact they are redundant is a deep and interesting property of the van Laarhoven representation. So I can see that application code might well want "traverseOf for a lens" without bringing the VL representation into it. And by having names for both we can more easily document the fact that e.g. traverseOf === toTraversalVL.

To summarise: I'm convinced we should add this, I'm just not sure what to call it. Neither traverseOfLens nor traverseOf' feels completely satisfactory, and nor does introducing an inevitable import clash by calling it traverseOf...

@ocharles
Copy link
Author

No worries! Naming is indeed hard. My intuitive thoughts are something like traverseOneOf, but now that I write it, it's sounds like "one of many traversals", so that's no good. traverseLens doesn't sound so bad though - I don't think you need the Of once you're talking about lenses.

@phadej
Copy link
Contributor

phadej commented Nov 25, 2022

The naming problem comes already from how to name a class:

class Traversable t => Singleton t where
  -- the member really should be verb... compare: traverse, map, ..., ...
  singleton :: Functor f => (a -> f b) -> t a -> f (t b)

It only has instances for types isomorphic to (c, ) for some c. (Thus I call it singleton - traversable with a single element).

Agda calls it Decoration with traverseF: https://hackage-search.serokell.io/viewfile/Agda-2.6.2.2/src/full/Agda/Utils/Functor.hs#line-56 and I couldn't (quickly) find anything else on Hackage.

@phadej
Copy link
Contributor

phadej commented Nov 26, 2022

And FWIW, traverseOneOf is particularly bad name, as there is traverse1 in the wild, which does something else what traverseOneOf would do, even as optics doesn't support Traversable1 things.

EDIT: In fact AffineTraversal has similar function as well, atraverseOf.

So we have:

  • traverseOf
  • atraverseOf
  • traverse1Of (in theory)
  • toLensVL - and this name doesn't fit the pattern atm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants