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: initialise funcs with custom type names #36855

Closed
carnott-snap opened this issue Jan 29, 2020 · 10 comments
Closed

proposal: Go 2: initialise funcs with custom type names #36855

carnott-snap opened this issue Jan 29, 2020 · 10 comments
Labels
Milestone

Comments

@carnott-snap
Copy link

carnott-snap commented Jan 29, 2020

Would you consider yourself a novice, intermediate, or experienced Go programmer?

Intermediate to experienced.

What other languages do you have experience with?

  • Java/Kotlin
  • Rust
  • JavaScript/TypeScript
  • Python
  • Haskell

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

This change adds a new way to construct functions, so implicitly it will increase the total knowledge require to understand all of Go. It is a feature that can be obscured from beginners, and benefit intermediate to experienced users.

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

There are no github issues for this exact proposal.

#21670 and #25860 only seeks to solve the implement an interface example below. #21670 only targets single method interfaces, while not adding any language changes, where #25860 seeks to solve this for all interfaces, with a new (somewhat complex) syntax.

Who does this proposal help, and why?

This helps developers that create anonymous functions that implement defined types.

Is this change backward compatible?

Yes, the func types currently cannot take a body.

Show example code before and after the change.

// before
http.Handle("", http.HandleFunc(func(ResponseWriter, *Request) {})) // or http.HandleFunc("", func(ResponseWriter, *Request) {})
err = filepath.Walk("/", func(path string, info os.FileInfo, err error) error { return nil })

// type function func(one, two string) string
// func (f function) default() string { return s("one", "two") } 
_ = function(func(from, to string) string {return from+to}).default()
// after
http.Handle("", http.HandlerFunc{}) // implement an interface
err = filepath.Walk("/", filepath.WalkFunc{ return nil }) // simpler initialisation
_ = function{return from+to}.default() // direct method set access

What is the cost of this proposal? (Every language change has a cost).

This change will add new syntax, thus complexity. This is most clear in the use of the default func parameter names, function{return from+to}, however, this feels very similar to the existing unkeyed struct literal syntax: type structure struct { u uint }; _ = structure{5}. This will also require some changes to existing symbols, like type HandlerFunc func(r ResponseWriter, r *Request).

How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?

As the syntax of the language is extended, in a compatible manner, all these tools would need minor updates.

What is the compile time cost?

While this will add complexity to the compiler, it is pure syntax sugar, so it should not be too expensive.

What is the run time cost?

Should be negligible, as this is syntax sugar.

Can you describe a possible implementation?

I am not familiar enough with the internals of the compiler to make a concrete suggestion, but high level:

  • if you see a given Type that is of type func followed by a body: Type{ /* ...*/ }
  • convert into this AST: Type(func(/* ... */) (/* ... */) { /* ... */ })

Do you have a prototype?

No

How would the language spec change?

The declarations section, not totally sure where, would need to call-out that Type { Expression ( ; Expression ) } is valid.

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

This change will extend the way that functions can be initialised. It should not break any existing features, but can simplify some existing code.

Is the goal of this change a performance improvement?

No.

Does this affect error handling?

No.

Is this about generics?

No.

@gopherbot gopherbot added this to the Proposal milestone Jan 29, 2020
@D1CED
Copy link

D1CED commented Jan 29, 2020

See also this issue: #21498

@ianlancetaylor ianlancetaylor changed the title proposal: initialise funcs with custom type names proposal: Go 2: initialise funcs with custom type names Jan 29, 2020
@ianlancetaylor ianlancetaylor added v2 A language change or incompatible library change LanguageChange labels Jan 29, 2020
@ianlancetaylor
Copy link
Contributor

Thanks for filling out the template.

I'm troubled by the fact that this permits expressions to refer to identifiers that are only defined far away in a different package. It means that changing the name of an unexported identifier in one package can break a different package that the author may not even know about. One of the goals of Go is to support programming at scale, and this suggestion seems to make that significantly harder.

@carnott-snap
Copy link
Author

carnott-snap commented Jan 29, 2020

I'm troubled by the fact that this permits expressions to refer to identifiers that are only defined far away in a different package. It means that changing the name of an unexported identifier in one package can break a different package that the author may not even know about.

Can you clarify what unexported identifiers you are referring to?

Though wagering a guess, I assume you mean the function parameters? If this is correct, then the implication, which I did not explicitly call out is: parameter names, once defined, are part of the external interface, ant changing them is a breaking change.

Since there is no usage for the names today, this is not a breaking change. I agree there is some cognitive load, but it is unambiguous what parameter names are, and this information is easily accessible via go doc. Furthermore, much like unkeyed struct literals or type inference, this change trades unambiguous ceremony for concise expressiveness.

This new syntax also does not rule out the existing one. There may be places, where authors prefer the existing syntax, but there are clear places where the new one is much more readable.

@neild
Copy link
Contributor

neild commented Jan 29, 2020

if you see a given Type that is of type func followed by a body: Type{ /* ...*/ }

I don't see how this works in terms of the language grammar. How does the parser know to parse the { /* ... */ } as a FunctionBody instead of a LiteralValue? The type of Type is not known at parse time.

@carnott-snap
Copy link
Author

I don't see how this works in terms of the language grammar. How does the parser know to parse the { /* ... */ } as a FunctionBody instead of a LiteralValue? The type of Type is not known at parse time.

This speaks to my limited knowledge of the compiler/parser implementation details. I cannot disagree that the ambiguity you describe may exist, but each block does have an unambiguous format. This seems similar to the current ambiguity between how struct, map, and [] all take {/* ... */} blocks, LiteralValue in you parlance, but they each have distinct formats and requirements. I am unclear is this is a hard block, or just something that needs to change in the parser implementation.

@ianlancetaylor
Copy link
Contributor

Can you clarify what unexported identifiers you are referring to?

Yes, I meant the function parameters. Sorry for not being clear.

I agree that this proposal means that changing the name of a function parameter changes the external API of an exported function. My point is that this is unlike anything in Go today. In other words, this is not a small syntactic addition; it's a subtle and far reaching change to the definition of the API of a Go package.

@ianlancetaylor
Copy link
Contributor

With regard to @neild 's point, the syntax of a composite literal is the same regardless of the type of the composite literal. The type of the composite literal determines the kind of values that can appears in the composite literal, but that does not affect the syntax any more than the fact that = supports values of all types.

It's a critical feature of Go that it be possible to parse the language without using a symbol table, so I do think that this is a blocker for the suggested syntax.

@ianlancetaylor
Copy link
Contributor

As noted above, this is a subtle change to how the Go API works across packages, and the syntax is most likely ambiguous. Also, the proposal does not have strong support based on emoji voting. For these reasons this is a likely decline. Leaving open for four weeks for further comments.

@carnott-snap
Copy link
Author

carnott-snap commented Feb 5, 2020

I can understand these concerns, and agree the additional exposed symbols represent a coup de grâce.

Upon review of the added features (interface implementation, simple initialisation, and method set access), the main benefit is interface implementation. This can be equivalently well served by #21670, however the discussion has stalled. Can I push for renewed discussion on that proposal?

Another option is #25860, since _ = http.Handler{func(ResponseWriter, *Request) { } } does not require the http.HandleFunc symbol, but it seems to fall victim of the same FunctionBody vs LiteralValue issue in this proposal.

@ianlancetaylor
Copy link
Contributor

No change in consensus, so closing.

@carnott-snap Sure, feel free to try to push forward on any other issue. Thanks.

@golang golang locked and limited conversation to collaborators Mar 10, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

5 participants