-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
slices: add DeleteFunc #54768
Comments
The intent is similar to these existing funcs:
|
In some uncommon cases, it could be useful to know the original index of an element, to decide if it should be removed or not. For this we would need to pass the index as an argument to the In my opinion we should not pass such an index because it is ambiguous, as DeleteFunc is destructive and changes the indices. To keep things simple, DeleteFunc would work only when elements are independent from each other, and the indices don't matter. |
This is traditionally called Filter or FilterInPlace. The decision was made to deliberately omit map and filter from slices pending discovery of best practices in real experience. With that said, I think this would be helpful, and maybe even under the name DeleteFunc. Filtering in place without generics is kind of tedious. The idiom I usually use looks like this: filteredSlice := original[:0]
for _, e := range original {
if cond(e) {
filteredSlice = append(filteredSlice, e)
}
}
original = filteredSlice It's not so bad, but it's not obvious to people unfamiliar with the idiom how it works, and it's a lot of lines for a simple idea. I would prefer: slices.DeleteFunc(&original, func(e E) {
return !cond(e)
}) In terms of the signature, I think |
I agree about the original slice not being useful for any common purpose, after having been changed by one of the destructive funcs.
all accept S and return S, instead of accepting *S and returning nothing. Maybe they do it that way to mimick |
For append, I think it makes sense that it returns a slice instead of taking a pointer because you may want to append to nil or a subslice etc. I don't think it makes as much sense for Compact/Delete/Insert. |
Having played around with implementing this generically myself, one thing that occurred to me is that another potential benefit for a generic DeleteFunc might be to go ahead and automatically zero out "deleted" slice elements so that any pointed-to memory can be GCed, avoiding the need to remember the "trick to it". The simplest way would just be to always zero from This brings up some more interesting questions. Because, if you have generics, and you want to put generic slice methods in the standard library, of course you would want to do the right thing for your users in one place if the cost isn't too high. For my part, I can definitely say that the documentation for slices.Delete is unsatisfying to me as a Go educator, since it's one more thing to have to explain upfront about a seemingly-simple process, and explaining Perhaps this is an argument for I think, I'd vote for either
|
You may be interested in #56351 which proposes to add a |
Yes, I think |
I agree with the sentiment that @flowchartsman the range of the leftover is always |
Maybe we should propose |
|
I think that pushback is misplaced. Just because slice expressions let us leave hanging references in a situation we might not want to isn't a strong argument in my opinion that a higher-level, generic function should not "do the right thing" for its expressed usecase. Slice expressions are, to my thinking, a basic feature that has no intrinsic intent beyond just "adjust the len and cap for this view into a backing array". They can be used in a variety of contexts, and it's up to the programmer to execute their intent by either zeroing out the elements or not, depending on what their plans are for that slice in the future. In addition, slice expressions do not change the positions of elements, so there's every expectation that everything in the backing array is just as you left it, and is the explicit reason we have three-argument slice expressions in the first place, to allow sharing a subset view without the worry that someone will accidentally With something called That's why my argument is that we either don't mess with the indices at all, in which case |
As an aside, I think exp/slices and exp/maps are an interesting window into the conflict between the general conservative attitude towards language changes and side-effects versus the expanded functionality of type parameters and how useful they can actually be if the desire is to do as little as possible behind the scenes.
|
Adding to the proposal process. The usual operation is Filter, though, in which the callback returns whether to keep the item, not to delete it. That might be a better answer, and then the Delete docs can point to Filter. The only problem then is finding a name that people feel is appropriate for filter-in-place. Some people think Filter should return a copy. It's especially confusing because Filter/DeleteFunc has to return a slice. Maybe call the in-place one slices.Keep and then have the Delete docs warn about not calling Delete in a loop and pointing to Keep? |
I was skeptical of the name "DeleteFunc" at first because "Filter" is usual, but I've been using it in my code since shortly after this issue was opened, and I've come around to liking it. It fits well with maps.DeleteFunc, which is also an in-place operation. I think the names Filter/Map/Reduce should wait until some generic iterator library is added to the standard library and be used there. Having slices.Map/Filter which return new slices encourages writing throw away intermediate slices. An iter library that looks something like this doesn't have that problem: it := iter.FromSlice(s1)
it = iter.Map(it, f1)
// Note: it needs to be it2 :=, if f1 outputs a different type
it = iter.Filter(it, f2)
it = iter.Map(it, f3) // etc
s2 := iter.ToSlice(it) // or e := iter.Reduce(it, initial, f4) |
This proposal has been added to the active column of the proposals project |
Given that maps has maps.DeleteFunc and 'delete' is the name of the operation on maps, and also that we have slices.Delete, adding slices.DeleteFunc with the sense of 'delete the values for which the function is true'. So DeleteFunc is probably better than Keep, and both are much better than Filter. |
Based on the discussion above, this proposal seems like a likely accept. |
No change in consensus, so accepted. 🎉 |
Change https://go.dev/cl/483175 mentions this issue: |
Change https://go.dev/cl/509236 mentions this issue: |
DeleteFunc was added to the standard library for the 1.21 release. Add it here in x/exp for people still using earlier releases. For golang/go#54768 Fixes golang/go#61327 Change-Id: I3c37051c289f46b0068bc1ee5da610149c59cd22 Reviewed-on: https://go-review.googlesource.com/c/exp/+/509236 Run-TryBot: Ian Lance Taylor <iant@golang.org> Run-TryBot: Ian Lance Taylor <iant@google.com> Reviewed-by: Cherry Mui <cherryyz@google.com> Auto-Submit: Ian Lance Taylor <iant@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@google.com> Reviewed-by: Eli Bendersky <eliben@google.com>
Fixes golang#54768 Change-Id: I588ae33c13e0bbd9d324c11771667b22a864047d Reviewed-on: https://go-review.googlesource.com/c/go/+/483175 Reviewed-by: Ian Lance Taylor <iant@google.com> Auto-Submit: Ian Lance Taylor <iant@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Eli Bendersky <eliben@google.com> Run-TryBot: Ian Lance Taylor <iant@google.com>
Fixes golang#54768 Change-Id: I588ae33c13e0bbd9d324c11771667b22a864047d Reviewed-on: https://go-review.googlesource.com/c/go/+/483175 Reviewed-by: Ian Lance Taylor <iant@google.com> Auto-Submit: Ian Lance Taylor <iant@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Eli Bendersky <eliben@google.com> Run-TryBot: Ian Lance Taylor <iant@google.com>
I propose we add this new func to golang.org/x/exp/slices :
The text was updated successfully, but these errors were encountered: