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: An Improved, Golang-Cohesive Design for Named Arguments #12296

Closed
codeblooded opened this issue Aug 24, 2015 · 14 comments
Closed

proposal: An Improved, Golang-Cohesive Design for Named Arguments #12296

codeblooded opened this issue Aug 24, 2015 · 14 comments

Comments

@codeblooded
Copy link

Named parameters have been requested in the past, and they have been rejected with good reason. However, I have approached this language feature from a different angle. I feel like it would improve legibility and maintainability throughout Go libraries and executables.

I have written up a proposal on Google Docs here: goo.gl/8KdnEG. Some contributors have already begun commenting.

I just learned of the new proposal process. I would love some feedback. Should I go ahead and fork golang/proposals and modify the doc to meet the new template or wait?

@codeblooded codeblooded changed the title Proposal: An Improved Golang-Cohesive Design for Named Parameters Proposal: An Improved, Golang-Cohesive Design for Named Arguments Aug 24, 2015
@codeblooded
Copy link
Author

I will be cc-ing my responses to comments on Google Docs here for reference

@codeblooded
Copy link
Author

If I understand correctly, all arguments must be present with this proposal (which is different from "all present arguments must be named"). Please clarify. – @griesemer

Sorry. Yes, for implementation simplicity... all arguments must be named and present.

@bradfitz
Copy link
Contributor

Additional concern: so now the argument names become part of the API signature, and thus part of the Go 1 API contact? Currently they're just a documentation and implementation detail. But with this proposal, we could never rename them.

@codeblooded
Copy link
Author

Concern: named parameters encourage overly-long parameter lists.– @bradfitz

I'll be honest; It's possible. But are there not many language features that could be abused? The fact that functions are first class objects and are passed around could be abused. Someone could design everything using asynchronous callbacks encouraged by NodeJS. It's callback hell. It's not encouraged, but it's possible. Why would anyone ever use all of these callbacks over the asynchronous functions in Go? Why would anyone put too many parameters illogically? In my book, they both could have potential disadvantages.

As for renaming, you could offer aliases like Swift does: func Trade(initial a : Int, next b : Int) -> (Int, Int). Initial and next are the aliases used for named arguments. However, I do not think this is necessary. We should discuss this. If things are well named, they should be changed iff a contextual change has occurred. This would also deprecate. It encourages best naming practices.

@mikioh mikioh changed the title Proposal: An Improved, Golang-Cohesive Design for Named Arguments proposal: An Improved, Golang-Cohesive Design for Named Arguments Aug 24, 2015
@minux
Copy link
Member

minux commented Aug 25, 2015 via email

@abglassman
Copy link

A compromise that would be in line with other recent changes to the language would be to infer from the func signature the type of struct literals passed in, as for elements of slices and maps, e.g.:

type Params struct {
    a int
    b string
}

func MyMethod(p Params) {
   ...
}

func main() {
    MyMethod({a: 42, b: "Type-ing without typing!"})
}

This still provides a relatively lightweight named-arg-like syntax for calling the function, though it does require declaring the struct type.

@minux
Copy link
Member

minux commented Aug 25, 2015

If we really want to do something here (I suppose we're not),
I think we can add the following two new syntaxes:

  1. Allow anonymous structures as function argument, e.g.:
    func f(struct { arg1 int; arg2 string }) {
    // use arg1 and arg2 as if they are real arguments
    }
  2. Automatic infer struct types at function call (i.e. elide the
    type name)
    f({arg1: 1, arg2: "hello, world"})

It provides both named parameter and default (zero) parameters.

@jimmyfrasche
Copy link
Member

Allowing elision of the type in a composite literal (struct or otherwise) in a function call covers everything without adding new concepts to the language. The only syntactic difference is there'd have to be outer {} but that seems a small price and would allow things like:
points({1, 2}, {3, 4}, {}) //where func points(ps ...image.Point)
which seems just as useful to me as having named parameters.

It has the same problem of any such proposal though where it makes it hard to tell what the type of the literal is at a glance.

But you could say that for every other argument to a function, including composite literals without the type elided as they could be used either for that type or are being assigned to an interface. That is, is
f(Point{0, 1})
taking an image.Point or a fmt.Stringer?

Cheney's "functional options" pattern provides a nice alternative to structs as named parameters that can be useful in either case: http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

@griesemer
Copy link
Contributor

There's much debate and feedback already on the linked Google Docs. @codeblooded perhaps you may want to consolidate the feedback in one place or suggest where (here or in the Google doc) you want additional input.

There is a deep connection between struct (also called records in other languages) and function parameters (= which are part of the activation record ). It is very intriguing to have similar notation for setting up structs and invoking functions.

I do like that the proposal is pretty minimal, but that said, here are some of the concerns brought up so far, plus a few new ones:

  • Exported functions cannot change their parameter names anymore or risk to invalidate existing code that uses those names (mentioned by @bradfitz ).
  • Named arguments lead to overly long parameter lists ( @bradfitz ).
  • What about variables of function types? At the moment, one can assign functions with different parameter names to assignment-compatible function variables. Do parameter names have to match (and thus become part of type identity)? ( @nigeltao )
  • When should one use named arguments, and when not? Easily can lead to needless style-guide wars.
  • What about variadic functions? How do I provide arguments for the variadic parameter? Or should variadic functions be excluded from this?
  • What about functions with unnamed parameters? (We cannot use the type names as in structs since the same parameter type may occur more than once). We would have to exclude those functions as well.

My conclusion so far, certainly as far as language design is concerned: When in doubt, leave it out!

I think permitting automatic deduction of a struct composite literal from assignment context (not just when used inside another composite literal) has a much higher chance of being adopted (@minux, point 2, I also think there's already a proposal or discussion about this elsewhere). This would permit struct parameters and simple passing of struct values w/o the need of the struct type. E.g.:

type Point struct{x, y int}
func Distance(p Point) float64 { ... }
func Distance(struct{x, y int}) float64 { ... } // note that we already permit this
...
Distance(Point{1, 2}) // currently permitted
Distance(Point{x: 1, y: 2}) // currently permitted
Distance({1, 2}) // would be permitted if struct type were inferred
Distance({x: 1, y: 2}) // would be permitted if struct type were inferred

@aclements
Copy link
Member

I second @minux's comment (#12296 (comment)), but would like to point out that only struct type elision (point 2) is necessary, since anonymous struct types can already be declared in a function parameter list and the function can simply give that argument a name and access its fields like usual. I think @griesemer may have been thinking this, but to carry it through, the following is already possible:

func Distance(args struct{X, Y float64}) float64 {
  return math.Sqrt(args.X*args.X+args.Y*args.Y)
}

I think this has several advantages over this proposal. It doesn't expose any API that isn't already exposed, so people don't find themselves locked in to their argument name choices. It upholds that rule that public identifiers must be capitalized. It has the dual properties that zero values can be omitted and the API can be extended with more fields in the future, which is a very common application for named parameters. And, the concept and syntax mirror the type elision that we already permit in map, array, and slice literals.

@griesemer
Copy link
Contributor

@aclements Exactly my thoughts. See also my example code (3rd line, use of a struct literal in parameter list).

@codeblooded
Copy link
Author

What about variables of function types? At the moment, one can assign functions with different parameter names to assignment-compatible function variables. Do parameter names have to match (and thus become part of type identity)? (@nigeltao and @griesemer)

You're right. I agree. This would break function assignment with identical arguments and different names, unless we used aliases which I think is overkill.

After much thought, I have found Inferring struct types at function call (addressed by @abglassman @minux @jimmyfrasche @griesemer @aclements) to be the most elegant compromise and a solution to this problem. I think it organizes it well, and I think it does not result in any clashes with existing Go-ideology.

Are there any objections or feedback on this idea? Should I proceed to create a golang / proposal?

@nigeltao
Copy link
Contributor

Yeah, I think it should be a separate proposal.

@griesemer
Copy link
Contributor

@codeblooded Yes, please, start a separate proposal. Thanks.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

10 participants