# filteredHas combinator #806

Open
opened this Issue Jun 5, 2018 · 12 comments

Projects
None yet
4 participants
Contributor

### yairchu commented Jun 5, 2018

 I'm suggesting this combinator: ``````filteredHas :: Lens.Fold s i -> Lens.IndexedTraversal' i s s filteredHas fold f val = case val ^? fold of Nothing -> pure val Just proof -> Lens.indexed f proof val `````` It can replace code like this: ``````Lens.filtered (Lens.has part) %~ \x -> something (x ^?! part) x `````` With `filteredHas part %@~ something`. I think it's quite useful and am using it on an expression tree sugaring pass. Disclaimer: It's quite possible it already exists and I just haven't found it, or has a very succinct way to write it that makes it unnecessary. I often have difficulty finding combinators in `lens`.

Owner

### ekmett commented Jun 5, 2018

 It doesn’t really pass the Fairbairn threshold for me, but it’s come up enough that I’m open to it. Maybe filteredBy would read better?
Contributor

### yairchu commented Jun 5, 2018

 `filteredBy` LGTM, shall I create a PR?

### yairchu added a commit to lamdu/lamdu that referenced this issue Jun 5, 2018

``` Use lens combinator filteredBy ```
`see ekmett/lens#806`
``` 97ebe82 ```
Collaborator

### glguy commented Jun 5, 2018

 You'll need to: Document that this is generally going to be an invalid Traversal (a la filtered) Fix the type to have a monomorphic argument instead of a `Fold` This could perhaps be improved with the type: `filteredBy :: (a -> Maybe b) -> IndexedTraversal' b a a` to most carefully capture the behavior. Then the fact that only a single value will be used will be evident from the use of `preview` if a `Fold` is being used in the first argument.
Owner

### ekmett commented Jun 5, 2018

 The problem with `filteredBy` taking `(a -> Maybe b)` is that then the common usecase still requires a call to `preview` inside it, exploding it back to the same number of "words" has before with `has`.
Owner

### ekmett commented Jun 5, 2018

 `filteredBy (preview x)` is longer than `filtered (has x)`
Collaborator

### glguy commented Jun 5, 2018

 That's true that it's longer, but the `filteredBy` version is supposedly more useful because it puts the extracted value into the index. Access to that value in the index is the only reason I think it's worth defining this in the first place.
Owner

### ekmett commented Jun 5, 2018

 @yairchu: Subject to the two bullet points raised by @glguy, sure.
Owner

### ekmett commented Jun 5, 2018

 I've been asked for some flavor of this about every 2-3 months for a couple of years now. Exposing the filtered part as an index finally gave me enough of an excuse to say yes. ;)
Contributor

### yairchu commented Jun 7, 2018

 I'm stuck on the PR, due to having trouble to make the types work for this simple example which I came up with for the doc-test: ``````>>> [(Just 2, 3), (Nothing, 4)] <&> filteredBy (_1 . _Just) . _2 %@~ (*) `````` This neither works with the type mentioned above nor with the more general ``````filteredBy :: (Indexable i p, Applicative f) => Getting (First i) a i -> p a (f a) -> a -> f a `````` I'm afraid I haven't yet mastered the different lens types and need some advice here.. (Note that I have been successfully using this combinator in my own code and haven't noticed this issue)
Collaborator

### glguy commented Jun 7, 2018

 @yairchu `_2` is a plain `Lens`, it's not an `IndexPreservingLens`, so you need to write `filteredBy (_1 . _Just) <. _2` if you want to use the index. `filteredBy :: Getting (First i) a i -> IndexedTraversal' i a a`
Contributor

### yairchu commented Jun 7, 2018

 @glguy oh oops :) thanks!

### yairchu referenced this issue Jun 7, 2018

Open

 This is also very useful for indexing one part of the structure while modifying another, without any filtering, e.g if we want to divide a monster's health by its age: ``````monster .> filteredBy monsterAge <. monsterHealth %@~ (/) `````` I would also say that traversing via prisms/etc is also a similar kind of "filter" to this one. Perhaps it would make sense to: Rename `filteredBy` to `toIndex` Instead of putting the first match into the index, behave like (^.) and put the `mconcat` of the matches