-
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 Concat #56353
Comments
a := []int{1, 2, 3}
b := []int{4}
c := slices.Concat(a[:2], b, a[2:]) What is the value of For example, consider the following (perhaps naive) implementation: func Concat[S ~[]E, E any](dst S, slices ...S) S {
for _, s := range slices {
dst = append(dst, s...)
}
return dst
} This will result in |
Isn't |
Append can't concatenate multiple slices. This means you need to jump through hoops to precalculate the capacity and to make a one-liner you end up with awkward things like |
I think it's only worth doing Concat if it does one shot preallocation, like func Concat[S ~[]E, E any](dst S, ss ...S) S {
c := 0
for _, s := range ss {
c += cap(s)
}
dst = Grow(dst, c)
for _, s := range ss {
dst = append(dst, s...)
}
return dst
} If you run this, you get 1, 2, 4, 3. |
@carlmjohnson I don't think it should use Really it should be |
😅 You're right! |
I think at a certain point, if the user is clobbering the slice they are reading from, it's a user error. You can probably make up an example using slices.Insert that is confusing too. |
@carlmjohnson It would be all too easy for someone not to realize that the first parameter is special in that regard, especially because it isn't in other languages that have similar APIs. And due to the nature of What if it were a method instead of a function, to reinforce the fact that the first parameter is actually the output buffer? type Buffer[T any] []T
func (buf Buffer[T]) Concat(ss ...[]T) Buffer[T] {
size := 0
for _, s := range ss {
size += len(s)
}
if size == 0 {
return buf
}
buf = Grow(buf, len(buf)+size)
for _, s := range ss {
buf = append(buf, s...)
}
return buf
} (This type name is just for example purposes.) Then it would be |
The objection to destination slices already applies to several functions in exp/slices: Insert, Delete, the upcoming Replace, and arguably Grow and Clip (although not as confusing as the others). If you think it's causing bugs, you should open an issue to make a v2 of the package before the current slices API gets into the standard library. |
Yes, it is possible to break them, but it is a bit more contrived. https://go.dev/play/p/4lLBHhxrfdN Regardless, adding a typedef with methods does not itself necessitate a v2. It just means some (or all) of the existing functions would presumably be deprecated. |
I feel like two versions of concatenation could make sense - a minimally complicated, more variadic 'Append'-like concatenation, and a profligately eager, maximally allocating 'Collect'-ing concatenation: |
I don't think it makes sense to have two very similar concatenation functions. |
Especially because the |
@DeedleFake The Go language spec defines |
It could, but only for the case of a single spread operator being used. It doesn't seem that different to me to just having multiple elements without using the spread operator. |
The edit distance in LOC from I think what I'm trying to suggest is that, on a first glance |
It cannot. func foo(x ...int) {
// ...
}
a := []int{1, 2, 3}
b := []int{4, 5, 6}
foo(a..., b...) // cannot work Within With regards to the original proposal, I think naming it something like |
Right, that's what I said. It wouldn't change anything for existing cases where only a single spread operator is used, which is all that's currently legal. |
Discussion about passing both |
|
I think we should consider this proposal together with #4096, since these are two different solutions to the same problem. Personally, I would much prefer to have #4096, since that feels like a natural and ergonomic extension to the way people already concatenate multiple slices. While we can do this with a generic function, doing so creates two different ways to do almost the same thing, where neither subsumes the other. It also has some (probably slight) performance disadvantages compared to doing it directly in |
This proposal has been added to the active column of the proposals project |
Change https://go.dev/cl/504882 mentions this issue: |
well this should impl. the proposed api: #61817 correct me if I did messed up somewhere :) |
Change https://go.dev/cl/516656 mentions this issue: |
@6543 There is already an implementation; see the linked change list in comment #56353 (comment) |
@fzipp yes but it does not implement the proposed api and looks stale |
It implements the approved API. It's not stale, just waiting for the Go release cycle: https://github.com/golang/go/wiki/Go-Release-Cycle |
Maybe this function should be specially optimized, just like |
If you have a performance improvement to benchmark, that can be a new issue. |
It is impossible to write normal benchmarks without hacking the runtime code. Someone ever hacked it. There is no need to create a new issue. |
Fixes golang#56353 Change-Id: I985e1553e7b02237403b833e96fb5ceec890f5b8 GitHub-Last-Rev: 96a35e5 GitHub-Pull-Request: golang#60929 Reviewed-on: https://go-review.googlesource.com/c/go/+/504882 Auto-Submit: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
Fixes golang#56353 Change-Id: I985e1553e7b02237403b833e96fb5ceec890f5b8 GitHub-Last-Rev: 96a35e5 GitHub-Pull-Request: golang#60929 Reviewed-on: https://go-review.googlesource.com/c/go/+/504882 Auto-Submit: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
Fixes golang#56353 Change-Id: I985e1553e7b02237403b833e96fb5ceec890f5b8 GitHub-Last-Rev: 96a35e5 GitHub-Pull-Request: golang#60929 Reviewed-on: https://go-review.googlesource.com/c/go/+/504882 Auto-Submit: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
@ianlancetaylor / @earthboundkid could one of you elaborate on what this comment means and why we shouldn't be using https://go-review.googlesource.com/c/go/+/504882/3..6/src/slices/slices.go#b510
|
This comment was marked as off-topic.
This comment was marked as off-topic.
Still not quite following, what do we mean by rounded up to the next size class? Do we mean:
|
This comment was marked as off-topic.
This comment was marked as off-topic.
@earthboundkid func BenchmarkSortStrings(b *testing.B) {
s := []string{"heart", "lungs", "brain", "kidneys", "pancreas"}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
sort.Strings(s)
}
} Therefore, I believe that this post is no longer valid. In this case, make seems to be superior in terms of performance, and there doesn't seem to be a significant difference in readability either. Can you explain in more detail why slices.Grow should be used in this scenario please? |
This is a closed issue. Your benchmark is invalid because sort.Strings isn't relevant to slices.Concat and the slice is sorted after the first loop. If you can make a change with better performance, submit the change with a benchmark from benchstat. |
@OhJuhun The current design of the
Because of these facts, the |
This came up in the discussion of #55358 and prior discussions. It would be nice to have a simple, generic way to concatenate a slice of slices.
Here's the proposed signature:
Usage:
The text was updated successfully, but these errors were encountered: