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

x/exp/maps: Copy should use two generic map types #52593

Closed
aarondl opened this issue Apr 27, 2022 · 11 comments
Closed

x/exp/maps: Copy should use two generic map types #52593

aarondl opened this issue Apr 27, 2022 · 11 comments

Comments

@aarondl
Copy link

aarondl commented Apr 27, 2022

There is an argument for having the Copy function in the x/exp/maps package take two generic map types.

// Current
func Copy[M ~map[K]V, K comparable, V any](dst, src M)
// Proposed
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2)

The reason for this is the same reason behind Equal and EqualFunc allowing two disparate map types. A programmer might decide that the semantics of the two types do not matter and they still wish to compare or copy/merge them.

Without the second generic map type, the following fails:

type A map[string]string
type B map[string]string

maps.Copy(make(A), make(B)) // compile error

Presumably the decision was made for Equal and EqualFunc for the same reasons that the decision to change this would be made. I could not find the reasoning behind the decision to have two map types for the equality functions in the first place but that would provide guidance on creation of future APIs similar to Equal/Copy.

As it is, it seems to make sense to either make Equal and EqualFunc take a single generic map type, or to make Copy take a second.

@gopherbot gopherbot added this to the Proposal milestone Apr 27, 2022
@ianlancetaylor ianlancetaylor added this to Incoming in Proposals (old) Apr 27, 2022
@blackgreen100
Copy link

blackgreen100 commented Apr 28, 2022

I disagree with this proposal. IMHO there is a semantical difference between Equal/EqualFunc and Copy that should be preserved on the function signature.

When comparing two maps for equality, m1 and m2 are "equally" important, in the sense that neither will be modified and for the purposes of the return boolean, it doesn't really matter which one is different. Your code may consider one of them as preeminent, but this isn't relevant to the function implementation.

With Copy instead, the first map dst is indeed modified, so there is a preeminence relationship. Whether it is dst over src or the other way around isn't necessarily important. What's important is that the relationship exists, hence it makes sense that both have the same type (parameter) on the function signature.

This way if you have some specific code where the types don't matter, you would use an explicit conversion:

	a := A{"foo": "1"}
	b := B{"bar": "2"}
	maps.Copy(a, A(b))

Note that in real code the type declarations may be far away from the point of usage, so forcing the explicit conversion makes for clearer code.

@aarondl
Copy link
Author

aarondl commented Apr 28, 2022

I prefer to think of it more practically: What do we gain as writers and readers of code?

Is it more clear as a reader if there's an explicit conversion? It doesn't seem so. If you see maps.Copy() it's pretty clear what that's doing irregardless of the two types involved. Is it important when reading the code to know the types are different at that point? Probably not, it seems more important that they're actually the same.

Does a writer gain any safety? Could it prevent a mistake where I have three maps, two of the same type and one of a second and I accidentally put in the wrong variable? It could - though it seems unlikely that this case is going to arise frequently enough to make decisions based on that possibility alone. Furthermore it may not be possible to access the map types to perform the conversion since they would have to be exported, and then you've no recourse remaining other than copy-pasting the implementation of maps.Copy - though not having have access to both map types is equally unlikely as juggling 3 maps with only 2 being of compatible type, but the consequences are certainly grating if it occurs.

I see this as a simple improvement in ergonomics. If I'm calling maps.Copy on these two variables of different types I probably have a pretty good idea of what I'd like to do with them and the extra conversion doesn't seem helpful to code writers (for safety) or readers (for readability) in my estimation. I have no statistics to back up these claims, just my own experience which may not weigh enough to get this proposal accepted in the face of disagreements.

@DeedleFake
Copy link

Since maps.Copy() is essentially a map analogue of the built-in copy() function, it makes sense for it to have the same semantics as that, and that works just fine on compatible slices of different types.

@rsc
Copy link
Contributor

rsc commented May 4, 2022

We added the M type parameter to match the many other functions in the package.
If that poses a problem for Copy, instead of having M1, M2, we could have no M parameter at all - just K and V.
Because the maps are only passed in, not returned, that will handle named maps automatically.

@rsc
Copy link
Contributor

rsc commented May 4, 2022

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

@rsc rsc moved this from Incoming to Active in Proposals (old) May 4, 2022
@blackgreen100
Copy link

blackgreen100 commented May 4, 2022

No parameter at all would make it inconsistent with other functions signatures, and actually lose flexibility in the (rather uncommon) case of declaring variables of function type.

type MyDictionary map[string]interface{}

fn := maps.Copy[MyDictionary]
// ...
fn(m)

If a change must be made, it's better to have M1 and M2 than no M parameter at all.

@rsc
Copy link
Contributor

rsc commented May 11, 2022

Ian points out there are other functions with two map parameters, like Equal, so I take back my suggestion to have zero.

@rsc
Copy link
Contributor

rsc commented May 11, 2022

Code like

package maps
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2)

type MyDictionary map[string]interface{}
fn := maps.Copy[MyDictionary]

will not work with two parameters. You will have to write:

fn := maps.Copy[MyDictionary, MyDictionary]

This seems somewhat awkward. On the other hand, if you have one parameter, this fails:

type A map[string]string
type B map[string]string

maps.Copy(make(A), make(B)) // compile error

That doesn't bother me a lot because this doesn't type-check either:

var a A
var b B
_ = a == b // mismatched types

Of course if you really want to force it, you can do:

maps.Copy[map[string]interface{}](make(A), make(B)) 

or

maps.Copy[map[string]interface{}](make(A), A(make(B)))

On the other hand, Equal already takes two. (In fact Copy is the only function that takes two maps as arguments but only has one type parameter.) So probably Copy should match.

@rsc
Copy link
Contributor

rsc commented May 18, 2022

Based on the discussion above, this proposal seems like a likely accept.
— rsc for the proposal review group

@rsc rsc moved this from Active to Likely Accept in Proposals (old) May 18, 2022
@rsc rsc moved this from Likely Accept to Accepted in Proposals (old) May 25, 2022
@rsc
Copy link
Contributor

rsc commented May 25, 2022

No change in consensus, so accepted. 🎉
This issue now tracks the work of implementing the proposal.
— rsc for the proposal review group

@rsc rsc changed the title proposal: x/exp/maps: Copy should use two generic map types x/exp/maps: Copy should use two generic map types May 25, 2022
@rsc rsc modified the milestones: Proposal, Backlog May 25, 2022
@gopherbot
Copy link

Change https://go.dev/cl/408894 mentions this issue: maps: permit Copy between two diffent generic map types

@golang golang locked and limited conversation to collaborators Jul 6, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
No open projects
Development

No branches or pull requests

5 participants