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 set.Find method #18

Merged
merged 3 commits into from
Aug 4, 2024
Merged

Add set.Find method #18

merged 3 commits into from
Aug 4, 2024

Conversation

rickb777
Copy link
Contributor

These two methods are frequently present in other collection APIs (Scala and Kotlin for example).

The existing Has method is in one sense a special case, where the predicate function is an equality test. Exists and Forall allow any comparator function, not just a simple equality test.

Although Exists and Forall are logically similar, both are needed for completeness because they are not direct inverses of each other.

If you like this PR, I can do a similar one for slices.

@bobg
Copy link
Owner

bobg commented Aug 1, 2024

Thank you for this contribution! I have a few suggestions.

First, the names: Exists sounds like it does the same thing as Has. I suggest Any may be a better name (as in, do "any" members pass this test?). And Forall sounds too much like it does what Each does. For that I suggest All (as in, do "all" members pass this test?).

Second, you might consider a slightly different API. It's useful to know whether "any" or "all" members pass a given test, but it's more useful to know which one caused the test to pass or fail. Here's an API that can do that:

// First traverses s in an indeterminate order and returns the first element that makes the given function true.
// The boolean result is true if such an element is found, false otherwise.
func (s Of[T]) First(f func(T) bool) (T, bool)

Now s.Any(f) is just _, ok := s.First(f) and s.All(f) is _, notOK := s.First(invert(f)) (where invert inverts the sense of a given predicate).

Third suggestion: don't put this in set, put it in the iter package instead. That makes the operation more composable: anything that can produce an iter.Of[T] - a set.Of[T], the rows from a database query, lines of text, etc. - can be All'd and Any'd. You'd just have to write iter.Any(s.Iter()). Upside: you don't need All and Any methods in the slice package and a hundred other types of iterable containers.

The fourth suggestion is to wait a few more weeks for Go 1.23 to arrive. As you may know, it will include a major new feature, "range over function," that will make this module's iter package obsolete. I'm preparing a successor right now in anticipation, and plan to deprecate much of this module at that time. One thing to keep in mind: my naming suggestion above notwithstanding, the Go 1.23 convention is for containers to have an All method to produce an iterable sequence of their members (like what set.Of.Iter now does), so that method name should be considered taken.

@bobg
Copy link
Owner

bobg commented Aug 1, 2024

Oh, I just remembered: please see also #2 (comment)

@rickb777
Copy link
Contributor Author

rickb777 commented Aug 1, 2024

Thanks Bob. The First method is a good suggestion, solving the naming issue. As I mentioned, those names come from functional languages on the JVM; there's nothing special about them except that they'll be recognized in some circles.

However, I don't think the conversion of set to iter is efficient enough for people to be expected to use it. It makes sense to include a First method (or similar) in both set and slices to avoid conversion costs, rather than moving as much to iter as possible.

@rickb777
Copy link
Contributor Author

rickb777 commented Aug 1, 2024

I revised the PR. It implements Find (not First) due to it being an arbitrary choice.

@coveralls
Copy link

coveralls commented Aug 3, 2024

Pull Request Test Coverage Report for Build 10237222541

Details

  • 7 of 7 (100.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.07%) to 87.924%

Totals Coverage Status
Change from base Build 10228542245: 0.07%
Covered Lines: 1063
Relevant Lines: 1209

💛 - Coveralls

@bobg bobg mentioned this pull request Aug 3, 2024
@bobg
Copy link
Owner

bobg commented Aug 3, 2024

I don't think the conversion of set to iter is efficient enough for people to be expected to use it

You have a point. Sorry to be unclear with my fourth suggestion, which I intended to pre-empt the third suggestion: put Find into the upcoming seqs package, which will replace iter shortly after Go 1.23 arrives. Go 1.23's iterators don't have the performance problem you're concerned about here.

However, since that will require a new major version of this module, I have no objection to adding your change to the current version.

Kindly merge the latest master into your PR. It includes this change, needed for CI to work properly.

Copy link
Owner

@bobg bobg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you!

@bobg
Copy link
Owner

bobg commented Aug 4, 2024

Ah, the auth token used by CI doesn't have the permission it needs to add a comment to this PR (apparently because the change comes from a fork of this repo). I'll merge this change anyway and fix that later. Thank you again.

@bobg bobg merged commit 8b93207 into bobg:master Aug 4, 2024
1 of 2 checks passed
@bobg bobg changed the title Added set.Exists and set.Forall methods Add set.Find method Aug 18, 2024
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

Successfully merging this pull request may close these issues.

3 participants