Consider the following:

type Foo func(a int) int

func makeFoo1(...) Foo {
    return func(a int) int { ... }
}

func makeFoo2(...) Foo {
    return func(a int) int { ... }
}

This is for the most part fine, if the function signature is short. However, when it comes to longer function signatures for Foo, then there's lots to type (on the keyboard).

Proposal ##

Allow literal definition of functions of a named function type, if the parameter variables are already bound in the definition of the type:

type Foo func(a, b, c, d, e, f, g, h, i int, j, k, l, m string) bool 

func makeFoo1(...) Foo {
    return Foo { 
        fmt.Printf("%d\n", g) // uses `g`, which is pre-bound to the 7th argument by the type definition above
    }
}

So what happens if Foo doesn't have variables bound to the parameters?

type Foo func(int, int) int 

func makeFoo1(...) Foo {
    return Foo {...}
}

I have two suggestions:

  1. It fails to compile, because the named function type doesn't define a default variable name for each of the parameters.
  2. Allow late(r) binding (but still within the compile phase) of variable names to the parameters... something like this:
type Foo func(int, int) int

func makeFoo1(...) Foo {
    return Foo(a, b int) int { ... }
}

Currently ##

What the parser currently does is that it treats Foo{ in return Foo{ as a composite type, and Foo( in return Foo( as a function call. I think the latter is definitely the right call, and the late(r) bindings / rebinding suggestion is not that great. I also do realize that this does add a little complexity to the parser, but it definitely affords a much higher quality of life for daily programmers

Comment From: jaloren

What’s the practical use case for this in an actual code? I rarely see functions with more than four arguments. Anything that requires more usually means the function just takes a config struct.

Comment From: apparentlymart

If I understand correctly, this proposed feature would make the parameter names chosen in the signature be part of the significant API of the package defining the named function type, whereas today those names are exclusively for documentation purposes and so authors can freely change them if they think of better names later based on user feedback.

I would personally find it surprising for it to be considered a breaking change to select a different name for an parameter in the signature, or to remove the names and use just the types. I worry that lots of package maintainers will make this mistake and learn about this unusual language feature only when they get a bug report saying that a caller's code is now broken.

If the information being elided were syntactically "closer" to the definition that implies that information then I might evaluate this tradeoff differently. For example, I feel good about the ability to omit the type part of a nested composite literal when the type is implied by another composite literal directly enclosing it, but I think allowing such an implication to flow from a package-level declaration into an expression inside an unrelated function -- which might not even be in the same package -- makes it far too easy for changes in one part of a large system to have confusing impact on other subsystems far from the declaration being changed, which is a maintenance hazard.

Comment From: apparentlymart

Based on my understanding of the proposals process, I think this proposal should be based on the language change proposal template.

Comment From: zephyrtronium

Not identical, but very similar to #30931.

Comment From: seankhliao

see also #21498

Comment From: chewxy

What’s the practical use case for this in an actual code? I rarely see functions with more than four arguments. Anything that requires more usually means the function just takes a config struct.

Yeah you are right. The example I wrote was an extreme example for the sake of illustration. The actual code I wrote which led to opening of this issue looked something like this (it's long and filled with type params): func(expectations functional.Expectations[int, float64], indices *tensor.Dense[int], retVal *tensor.Dense[float64], right, left func(int, float64) (int, error)). I had 16 of these makeFoo equivalents to write.

Whether or not this is a code smell (the function signature probably is, I'm still in exploration mode), you gotta admit that with package names and long type names and potentially instantiations of generic data types, you will end up with very long function signatures. And yes while names can be shortened or aliased out, it's not always the most prudent thing to do.

I ended up writing a bit of elisp to generate the skeleton for my 16 makeFoo functions. But this is not ideal. Not everyone uses a programmable text editor.

Comment From: chewxy

Additionally, there's the issue of consistency. In Go, values of almost all the named composite types (arrays, maps, slices, structs, channels) can be instantiated by using its name, e.g.

type MyChan chan int
type SoC struct{ C MyChan }
type ListOfSoC []SoC
type MyMap map[int]ListOfSoC

... 

C := make(MyChan)
s := SoC{C}
l := ListOfSoC{s, s, s}
m := MyMap{0: l}

There's one composite type that is missing from having this ability: function types. For the sake of consistency, we should allow values of function types to be instantiated with their names too

Comment From: earthboundkid

For the sake of consistency, we should allow values of function types to be instantiated with their names too

You can do f := myFunc(nil). It's only illegal if you want to define the function body, which seems like a decent restriction since you need to name the arguments to use them.

If https://github.com/golang/go/issues/21498 were accepted, I think that would be a good enough solution.

Comment From: seankhliao

This means a cosmetic change in the argument names of a func type definition becomes a breaking change, potentially in a far away place

Comment From: jarrodhroberson

this is an awful proposal from a maintenance/comprehension/reading stand point.

"features" that do nothing but save keystrokes have exponential downsides when trying to maintain the code later, or read it to learn what it is doing. explicit is better than implicit. and competent IDEs like GoLand will auto generate the mapping by pressing CRTL+ALT+ENTER. whatever time implementing this would be much better spent on something way more useful like allowing arbitrary struct to be comparable by specifying a function for the struct.

Comment From: gopherbot

Timed out in state WaitingForInfo. Closing.

(I am just a bot, though. Please speak up if this is a mistake or you have the requested information.)