-
Notifications
You must be signed in to change notification settings - Fork 17.6k
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
proposal: slices: add Shift #64103
Comments
example: parsing
|
I like the idea of this -- I've hand-written inline variations of it a number of times. Based on my previous similar uses of code like this, I guess I'd expect to use it in a loop something like this: remain := thingThatProducesSlice()
for len(remain) != 0 {
next, remain, _ := slices.Shift(remain)
// Do something with "next"
} In this case I don't really need the boolean result because The name (I did a little survey of languages with a |
@apparentlymart Your example is wrong, it should be: remain := thingThatProducesSlice()
for len(remain) != 0 {
var next int
next, remain, _ = slices.Shift(remain)
} I wonder whether it would be better to pass the slice as a pointer instead, to avoid returning it from the function. |
Heh thanks @mateusz834. Amusingly, now you've pointed it out I remember making exactly that same mistake in at least one of the hand-written previous implementations I was referring to, so apparently for me at least that's an attractive nuisance. Making the function take a pointer and then overwrite it with a slice pointing one element further along does seem like it would address that problem, despite being a little unusual. I suppose it would make it a little like #64090, although voting doesn't seem favorable on that at the time I'm writing this. (Perhaps that's just because of how close to the builtin Pointer-based version: remain := thingThatProducesSlice()
for len(remain) != 0 {
next, _ := slices.Shift(&remain)
// ...
} This now seems temptingly close in shape to a traditional "for clause", since it has all of the parts: an initializer, a predicate, and a "next element" assignment. That made me try for a version without the |
I don't think it fits with the general convention to pass it as a pointer, although I do see the appeal in doing so. If you want a "traditional for clause", maybe something like this would work (while still allowing you to consume items within the loop): parts := thingThatProducesSlice()
for next, remain, ok := slices.Shift(parts); ok; next, remain, ok = slices.Shift(remain) {
// ....
} |
I'd also like to point out that the main appeal for using this is in inline if statements like the following (it's just as simple to use var next string
if next, rest, _ = slices.Shift(rest); next != "" {
// ...
} Or in a switch statement: switch next, rest, _ := slices.Shift(rest); next {
case something:
// ...
case whatever:
// ...
default:
// ...
} Or for taking the next value or a zero value if none is provided: whatever.Field, rest, _ := slices.Shift(rest) These three examples are cases where having a length check is error-prone and make the code more complex and less readable. |
So far it's seemed like all the examples we've been sharing with each other have just totally ignored the boolean Again I find myself unsure what to make of that, since it also doesn't seem right for it to just panic if someone calls it with an empty slice, but if that Does anyone have an example of a usage pattern that does rely on the |
Given that ok is always the same as |
You're probably right. I added it to match how most of the Go functions like this work, but looking back at my own uses of this, there's almost no cases where I would actually use I guess the point of Shift is primarily to shorten the cases where you need the first element, but you don't really care if it exists -- only if the value is something you're looking for. |
Seems reasonable. TakeFirst would be a more self-descriptive name. [Edit: see my next note.] |
This seems good to me: // TakeFirst returns the first element of s and s[1:]
// if len(s) > 0, otherwise it returns the zero value of E
// and s unchanged.
func TakeFirst[S ~[]E, E any](s S) (first E, rest S) {
if len(s) == 0 {
rest = s
return
}
return s[0], s[1:]
} |
Hmm, let's see what it looks like in practice for a queue:
Or:
Or:
In none of these cases is it as simple or efficient as the straightforward Go code. For the len=0 logic to be of any benefit we would need a variant such as this ugly thing:
I retract my enthusiasm. |
Yeah, to my mind the only version that's even worth considering is the pointer-based version. I prefer it to just panic on empty slice:
I use a helper like this in advent of code where I write little queue-processing functions and whatnot all the time. In normal/professional code, where it comes up a bit less frequently, the "plain Go" version
is only a tiny bit more verbose and seems fine (better than all the So overall this seems very marginal. |
I retract my support for TakeFirst. That seems persuasive that it's not a net savings. Oh well. |
I propose adding the following function to slices:
Like #64095, this is useful for parsing stuff.
Update: I think this is better without the
ok
return value.The text was updated successfully, but these errors were encountered: