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: spec: avoid repeating concrete type parameters when generic arguments are passed to generic functions #53138

Closed
kalexmills opened this issue May 29, 2022 · 5 comments
Labels
FrozenDueToAge generics Issue is related to generics Proposal TypeInference Issue is related to generic type inference
Milestone

Comments

@kalexmills
Copy link

Author background

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

  • What other languages do you have experience with?
    Java; Scala; C/C++; Python

Related proposals

  • Has this idea, or one like it, been proposed before?
    I don't believe so.

  • Does this affect error handling?
    No

  • Is this about generics?
    Yes

    • If so, how does this relate to the accepted design and other generics proposals?
      This is a proposal for an improvement to the inference procedure to reduce redundancy.

Proposal

  • What is the proposed change?

Today, whenever a generic argument is passed to a generic function, you must specify the concrete type at the call-site and repeat this type for each argument.

// declarations
type Generic[T any] struct { t T }

func Foo[T any](x Generic[T]) { }

// current usage
Foo[string](Generic[string]{t: "hello"})

This proposal is to modify the type inference procedure to allow the types specified at the callsite to be inferred for arguments.

// proposed usage
Foo[string](Generic{t: "hello"})

The utility of this change is more easily seen when passing generic functions to other generic functions.

// declarations
func Generic[T any](t T) { }

func Foo[T any](f func(T)) { }

// current usage
Foo[string](Generic[string]) 

// proposed usage:
Foo[string](Generic) 

This should improve readability by allowing users to make calls to generic libraries more tersely than they would need to today. It would also enhance the utility of generic libraries which seek to offer functional options to their users for configuration.

  • Who does this proposal help, and why?

Authors and users generic libraries, who want to be able to pass generic type parameters to generic functions without repeating themselves unnecessarily.

The main driver for this proposal is this function call, which is rather verbose (snippet below).

// call-site:
withOptions[string](t, Opts[string](), func(t *testing.T, opts []pipelines.OptionFunc[string]) { 
  // ...
})

// declarations involved:
type optionMap[T any] map[string][]pipelines.OptionFunc[T]

func withOptions[T any](t *testing.T, opts optionMap[T], f func(*testing.T, []pipelines.OptionFunc[T])) 
  • Please describe as precisely as possible the change to the language.

Whenever a generic argument X[T] is seen in a call to a generic function func Foo[T](X[T]), any concrete types seen in the parameter for calls involving Foo are applied during type inference for the arguments. This allows usage like Foo[string](X) to compile, whereas today, usage like Foo[string](X[string]) is required.

See the proposed change to the language spec below.

  • What would change in the language spec?

At first blush, adding a step to the current type inference procedure, before step 1 seems like it would avoid the duplication involved.

  1. If the generic type is an argument to a generic function; all values from the function's parameter list are included in the parameter map.

Other portions of the spec may need to be modified as well to take this change into account.

  • Please also describe the change informally, as in a class teaching Go.

  • Is this change backward compatible?
    Yes; since this change would just make syntax which is required today unnecessary.

  • Show example code before and after the change.

  • Before

See playground link containing the below code.

func Foo[T any](f func(T)) {
  // ...
}

func Bar[T any](t T) {
  // ...
}

type AStruct[T any] struct {
  ts []T
}

func Returns[T any]() AStruct[T] {
  return AStruct[T]{ts: make([]T, 2)}
}

func Foo2[T any](f func() AStruct[T]) {
  // ...
}

func main() {
        Foo[string](Bar[string])
	Foo2[string](Returns[string])
}
  • After
// new main method:
func main() {
        Foo[string](Bar)
	Foo2[string](Returns)
}
  • Orthogonality: how does this change interact or overlap with existing features?
    This change has the potential to interact with downstream stages of the type inference procedure, and this should be considered carefully.

  • Is the goal of this change a performance improvement?
    No.

Costs

  • Would this change make Go easier or harder to learn, and why?
    Unclear. On the one hand, one could argue that this may may Go somewhat easier to learn because having to specify the types in every case is a bit confusing; especially when there can only be one answer. Another argument may be that having to place explicit types everywhere is clearer, even when redundant.

  • What is the cost of this proposal? (Every language change has a cost).
    Time taken to implement and potentially unforeseen consequences of modifying type inference.

  • How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
    A non-zero number of them. I am unsure.

  • What is the compile time cost?
    Type inference could become slightly more expensive.

  • What is the run time cost?
    None.

  • Can you describe a possible implementation?

Before beginning type inference for a generic argument to a generic function argument; add any concrete types from the parameter list of the generic function to the parameter map. Track whenever this is done so that the compiler can also ensure that code is generated for these arguments.

  • Do you have a prototype? (This is not required.)
    No.
@gopherbot gopherbot added this to the Proposal milestone May 29, 2022
@seankhliao
Copy link
Member

I think this is #47868 or #50285 (I think we should only keep 1 for type inference based on usage context?)
cc @ianlancetaylor

@kalexmills
Copy link
Author

I think this is #47868 or #50285 (I think we should only keep 1 for type inference based on usage context?) cc @ianlancetaylor

They are all related, but this proposal suggests a specific solution via a concrete change to the language spec.

@ianlancetaylor ianlancetaylor added generics Issue is related to generics TypeInference Issue is related to generic type inference labels Jun 8, 2022
@ianlancetaylor ianlancetaylor changed the title proposal: Avoid repeating concrete type parameters when generic arguments are passed to generic functions. proposal: spec: avoid repeating concrete type parameters when generic arguments are passed to generic functions Jun 15, 2022
@griesemer
Copy link
Contributor

In your first example

// declarations
func Generic[T any](t T) { }

func Foo[T any](f func(T)) { }

// current usage
Foo[string](Generic[string]) 

// proposed usage:
Foo[string](Generic) 

the line

Foo[string](Generic[string]) 

can already be written as (playground link)

Foo(Generic[string]) 

There's no benefit in this example.

The same is true for your longer examples (modified playground link).

The benefit only is present when a generic function is passed to a function argument that is using no (or different) type parameters.

@ianlancetaylor
Copy link
Contributor

I think we can close this as a duplicate of #59338, which is newer but is written in terms of an updated form of type inference. Does anybody disagree?

@kalexmills
Copy link
Author

That sounds good to me! Thanks for the update!

(Also: My opinion is not the only one which matters; if this needs to be re-opened, please do so.)

@kalexmills kalexmills closed this as not planned Won't fix, can't repro, duplicate, stale Apr 3, 2023
@golang golang locked and limited conversation to collaborators Apr 2, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge generics Issue is related to generics Proposal TypeInference Issue is related to generic type inference
Projects
None yet
Development

No branches or pull requests

5 participants