I'm a little unsure what the most idiomatic way of dealing with destructive functions in Go is.
Let's say I have the following:
func ShuffleA(slice *[]Card) {
// shuffling code here, unimportant for the issue at hand
}
func ShuffleB(slice []Card) []Card {
// shuffling code here, unimportant for the issue at hand
}
ShuffleA
is destructive, while ShuffleB
is not.
The question is, which one is the more idiomatic to use in Go, and more importantly, if I have one, is there an idiomatic name for the other?
In Scheme destructive functions are suffixed with a !
, so there you'd have shuffle
and shuffle!
, which gives a clear indicator as to what's destructive and what isn't. Now, I know Go isn't Scheme, and I'm not looking to compare them. I'm just asking in case other people are going to use my code; I don't want the functions to go against what they expect them to do.
评论:
Fwippy:
anotherdonald:I think the destructive version is more idiomatic.
If you wanted to provide both, I'd suggest
func Shuffle(slice []Card){} func Shuffled(slice []Card) []Card{}
(inspiration from python's
sort
andsorted
).
TheTimegazer:I'm just a go beginner, but they have different uses, don't they? ShuffleA affects all data structures pointing at slice, while ShuffleB doesn't (assuming it just shuffles the contents of the array).
I don't remember having seen this mentioned in the context of naming, though.
mcouturier:well A and B only affect slices of that particular type, the only real difference between them is that one destroys the original slice by modifying it; while the other returns a new slice, leaving the original untouched
ultra_brite:Everywhere in core I have seen the second way. Check append(), bytes.Replace, etc...
TheMerovius:It doesn't matter, none is more idiomatic than the other. Suffixing a function with a ! is a matter of convention in Scheme. Go doesn't support ! in variable names.
danredux:When dealing with slices, it's pretty unidiomatic to use a pointer, but it still would be idiomatic to do a shuffle "destructively", so my answer is, neither, but instead
func ShuffleC(slice []Card) { // shuffling code here, unimportant for the issue at hand }
Shuffling doesn't need to change a slice, just it's contents, so don't take a pointer. See, for example, all the
sort.Interface
implementations.Only return a new slice, if what you want can not be done well in-place anyway.
string.Replace
is an example of something that cant be done in-place, so returning the new string is fine. Same forbytes.Replace
: In general, the resulting slice will have a different lengths from the input, so the slice would need to be modified.Another example of how to deal with this is
io.Reader.Read
. Strictly speaking it would need to modify the slice (because it only fills part of it). Instead, it does a completey different thing, which is to return an integer with the number of bytes filled.Overall, there is no convention around any of this. I'd encourage you to lean on the destructive side, though, because that way you give the choice of whether or not to allocate to the user of your package. Go is not a functional language, so the compiler will do less work around optimizing out such allocations. But in the end, you should do what makes most sense for your API. The signature will mostly make it clear whether the function is destructive or not, otherwise the documentation should tell you.
Nathanfenner:Mutate your arguments, it's more efficient.
The caller will copy anything it needs to keep.
NeverUse-YouPromised:If you're only shuffling, and not changing the length of the slice or replacing it with a slice allocated elsewhere in memory, there is no reason to pass a pointer to the slice. Slices are just (data, length, capacity) tuples (in some order). If you modify the values it points to, the "original" slice (which is an exact, shallow copy of the original) would be modified.
For example:
function Shuffle(slice []Card) { for i := range slice { j := rand.Intn(len(slice) - i) + i slice[i], slice[j] = slice[j], slice[i] } }
will work as expected and modify the passed-in slice.
Pointers to maps, slices, and interfaces aren't used much (although for example if you want to modify the length of a passed in slice, you would need to pass a pointer to it).
I would say that usually, if the function is simple, such as shuffling, it doesn't really matter what you do. If someone doesn't want their slice modified, they can just copy it before calling.
If the function is complicated, and, for example, also returns some other value, then it is probably undesirable to modify the arguments if it would be surprising.
tdewolff:Slices are reference types, kind of like hidden pointers. You don't need a pointer to a slice to be able to modify it within a function.
You do need a pointer if you add or remove items
