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

proposal: Go 2: make(...) should have the last (optional) parameter for configuring default values for all types (slices, maps and chans) #65332

Closed
1 of 4 tasks
rishabhkhemka opened this issue Jan 28, 2024 · 9 comments
Labels
LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@rishabhkhemka
Copy link

Go Programming Experience

Experienced

Other Languages Experience

C++, Python

Related Idea

  • Has this idea, or one like it, been proposed before?
  • Does this affect error handling?
  • Is this about generics?
  • Is this change backward compatible? Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit

Has this idea, or one like it, been proposed before?

No, as per preliminary (not extensive) research.

Does this affect error handling?

No

Is this about generics?

No

Proposal

make(...) should have the last parameter for configuring default values for all types (slices, maps and chans)

the behaviour can be simulated by wrapping the types with custom methods but is not ideal since we don't have operator overloading.

adding wrappers for configuring default behaviour goes against the readability aspect of the language.

Language Spec Changes

No response

Informal Change

make([]int, 2, 10, 1) // -> should fill the slice / underlying array with 1s
similarly for maps(default values) and chans(values for closed chans)

there might be an alternate way to introduce this via special constructors in the experimental packages.
I believe make is the most appropriate way for configuring the default behaviour.
I am fine with either.

Is this change backward compatible?

yes, make is already variadic

Orthogonality: How does this change interact or overlap with existing features?

No response

Would this change make Go easier or harder to learn, and why?

this doesn't add any cognitive overhead, nor does it remove any.
it does remove a lot of boilerplate code when there is need for configuring default behaviour of inbuilt data structures.

Cost Description

No response

Changes to Go ToolChain

No response

Performance Costs

No response

Prototype

No response

@rishabhkhemka rishabhkhemka added LanguageChange Proposal v2 A language change or incompatible library change labels Jan 28, 2024
@gopherbot gopherbot added this to the Proposal milestone Jan 28, 2024
@thediveo
Copy link

thediveo commented Jan 28, 2024

how does this work for maps, as the title explicitly mentions it? what does filling for a map even mean?

@rishabhkhemka
Copy link
Author

if mp is a map
mp[key] returns the zero value of the value type, that should be configurable / "filled", via the last parameter in the inbuilt make function

@seankhliao
Copy link
Member

this doesn't add any cognitive overhead, nor does it remove any.

this doesn't appear to be true, as now you can't know what m[key] for a nonexistent key will return without seeing how the map was created.

@thediveo
Copy link

if mp is a map mp[key] returns the zero value of the value type, that should be configurable / "filled", via the last parameter in the inbuilt make function

This doesn't make any sense to me, we have the ok pattern, and we have zero values. Changing the language and the runtime for a default non-zero value...?

@rishabhkhemka
Copy link
Author

this doesn't appear to be true, as now you can't know what m[key] for a nonexistent key will return without seeing how the map was created.

would slightly disagree here, i get your point though
if the default value was configured explicitly, it is the intention that existence check will not be required at places we can use the found value/default

rn, i need to do the following (or its equivalent)

if _, ok := mp[key]; !ok {
    mp[key] = defaultValue // non zero
}
 
mp[key].someMethod() // could be operator or anything

instead

mp[key].someMethod() 

now if you do want to check the what the default value is in a call hierarchy, you'll need to go to initialization as you said
if the existence check was required, it anyway needs to be explicit since zero values can't be used to verify existence => no code or logic change

for channels I can't do this that simply anyway, if I want to signal a non-zeroed default value from closed channels
for slices, extra loops are required whenever an initialization or cap expansion needs to be done

@apparentlymart
Copy link

apparentlymart commented Jan 29, 2024

This feels to me like multiple non-trivial proposals in one, although I acknowledge that some of them depend on others:

  1. Maps that have default values to return when accessing an element that isn't present, instead of returning the zero value.
  2. Channels that have a specified value to return if they are read from when they are closed, instead of returning the zero value.
  3. An extension of make that:
    • For slices, pre-populates the backing array with a given value.
    • For maps, sets the "default value" for the map as defined by item 1.
    • For channels, sets the "closed-read value" for the channel as defined by item 2.

Items 1 and 2 seem like far more significant additions to the language -- they affect behavior of those types globally, rather than just at one callsite -- and so I think this proposal is kinda burying the lede by focusing primarily on the syntax extension in point 3.

I'd suggest that it's perhaps better to start by proposing the new map and channel behaviors, describing how they would behave under all of the different operations that maps and channels support. The syntax for constructing maps and channels with those new features enabled is of course still an important part of the proposal, but I don't think it's the most important part.

(The behavior for slices seems a little tricky too, despite the ability to pre-populate those. Should a slice also "remember" its default value, in a similar way as for maps and channels, so that an out-of-bounds access can return that default value? Should excess capacity added by append be filled with the default? But at least if focusing only on slices it's more defensible to say that it's really only about pre-populating the initial backing array; that seems then like a variant of #65238 as originally proposed.)

@rishabhkhemka
Copy link
Author

rishabhkhemka commented Jan 29, 2024

it is non-trivial for sure.

I'd suggest that it's perhaps better to start by proposing the new map and channel behaviors, describing how they would behave under all of the different operations that maps and channels support. The syntax for constructing maps and channels with those new features enabled is of course still an important part of the proposal, but I don't think it's the most important part.

the way I see it - configuring the default via an optional parameter in make, is not a behavior change (as in replacement).
it is a non-intrusive feature addition(change) which I thought (may be it's esoteric) would be welcome along the lines of go's phillosophy.

there is no changing of the old behavior (old code imho will remain a 100% backwards compatible)
zeroing still happens if you don't pass the default explicitly
the intent is addition of a new behavior in a place it makes sense. or maybe in experimental packages. or both.

(The behavior for slices seems a little tricky too, despite the ability to pre-populate those. Should a slice also "remember" its default value, in a similar way as for maps and channels, so that an out-of-bounds access can return that default value? Should excess capacity added by append be filled with the default? But at least if focusing only on slices it's more defensible to say that it's really only about pre-populating the initial backing array; that seems then like a variant of #65238 as originally proposed.)

for slices append will need to take the default value into account, oob should remain as it is, aligning with the current behavior

@rishabhkhemka rishabhkhemka changed the title proposal: Go 2: make(...) should have the last parameter for configuring default values for all types (slices, maps and chans) proposal: Go 2: make(...) should have the last (optional) parameter for configuring default values for all types (slices, maps and chans) Jan 29, 2024
@apparentlymart
Copy link

What I meant in my previous comment is that the change to the behaviors of maps or channels has consequences beyond just the addition of a new parameter to make, each of which seems to require decisions to be made. For example:

  • How does the default value for a map or channel follow that object around? Is it an extra pointer in the "header" of the object? An additional pointer stored nearby something that the header already points to?
  • How does having a nonzero default value change the v, ok assignment mode for maps or channels? You did already propose an answer to this in a comment; I suggest including that answer in the proposal.
  • Does copying a map also copy the default value? Is it possible to choose whether or not that happens?
  • Should there be some way to directly test whether a map or channel has a default value? To retrieve the default value from a map directly, rather than as a side-effect of indexing?

The above are just some examples of what I mean about this being a more "global" idea, rather than just a change to the make signature. It is probably not an exhaustive list of implications of maps and channels having default values.

When I suggest proposing the default values idea as a whole, I mean a proposal that addresses the implications more broadly, showing that the new feature can fit in to all parts of the existing language.

@psnilesh
Copy link

psnilesh commented Jan 30, 2024

@rishabhkhemka it would be very helpful to see some real world examples that might benefit from this feature. Examples where returning non-zero default values would actually result in better code / design.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

6 participants