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: infer generic function type even when it's called #61712

Open
rogpeppe opened this issue Aug 2, 2023 · 4 comments
Open

proposal: spec: infer generic function type even when it's called #61712

rogpeppe opened this issue Aug 2, 2023 · 4 comments
Labels
generics Issue is related to generics NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Proposal TypeInference Issue is related to generic type inference
Milestone

Comments

@rogpeppe
Copy link
Contributor

rogpeppe commented Aug 2, 2023

As of current tip (fb6f38d), this code works:

package main

func main() {
	g(1, mkf)
}

func g[T any](x T, f func() func(T)) {}

func mkf[T any]() func(T) {
	return func(x T) {}
}

but this code fails, because cannot infer the type of mkf when it's been called:

package main

func main() {
	g(1, mkf())
}

func g[T any](x T, f func(T)) {}

func mkf[T any]() func(T) {
	return func(x T) {}
}

It's entirely possible that getting this to work might increase the complexity of the spec too much, but I'm raising this issue to track the possibility, because it's not an uncommon pattern. In particular, the functions used in the "functional options" pattern often return a closure that operates on the receiving type, but the lack of inference forces the caller to explicitly pass the type parameter in such cases. Here's a small example of that pattern: https://go.dev/play/p/kJJWceSqlNM?v=gotip

@griesemer

@gopherbot gopherbot added this to the Proposal milestone Aug 2, 2023
@rogpeppe rogpeppe added generics Issue is related to generics TypeInference Issue is related to generic type inference NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Aug 2, 2023
@griesemer
Copy link
Contributor

Slightly streamlined version to make the difference between the two cases obvious:

func _() {
	g1(1, mkf)    // this works
	g2(1, mkf())  // this currently does not work (cannot infer T)
}

func g1[T any](T, func() func(T)) {}
func g2[T any](T, func(T))        {}

func mkf[T any]() func(T) { panic(0) }

In the g2 case, the type of mkf would be inferred from the assignment of its result type func(T) to the 2nd argument of g2 (also func(T), but a different T of course). Unification would make all these T's "the same" and then, from the passing of 1 to T we would infer that T is int.

This is not particularly hard in this case.

The problem with inference across calls is that inference will have to work across arbitrarily deep nested calls. Consider:

func f[T any](T) T { ... }
var _ float64 = f(f(f(...f(1)...)))

The type of T in each f is float64. To infer this, the type checker needs to consider the entire stack of nested calls. This is not conceptually difficult, and quite possibly not too hard to describe in the spec either, but the type checker is (implementation-wise) simply not set up to do this at the moment because each call represents an expression in an expression tree and inference doesn't work across the entire tree, just on one level of the tree.

I think this is a duplicate of #50285.

@Merovius
Copy link
Contributor

Merovius commented Aug 2, 2023

I'll comment here for now, moving it to #50285 if this is closed: A concrete use case I have for this specific issue is shown in this example in a package of mine. The package makes it easier to compare (and thus sort) composite values. It does so, by composing getters and comparison functions. In the example, we have the line

cmp.ByFunc((*pb.Person).GetNumbers, cmp.SliceFunc[[]*pb.PhoneNumber](cmpNumber))

This is (I think) exactly the case from this issue: SliceFunc needs an explicit type-argument, even though it could be inferred from GetNumbers.

For easy context, the signatures involved are

package cmp
type Cmp[T any] func(a, b T) int
func ByFunc[C, F any](by func(C) F, cmp Cmp[F]) Cmp[C]
func SliceFunc[S ~[]T, T any](cmp Cmp[T]) Cmp[S]
var cmpNumber Cmp[*pb.PhoneNumber]

package pb
func (*Person) GetNumbers() []*PhoneNumber

@rogpeppe
Copy link
Contributor Author

rogpeppe commented Aug 2, 2023

FWIW I think that since it was decided that it was ok to propagate types from the target to the source for function types, going further and doing it for arbitrary expressions becomes harder to argue against.

I agree that this seems like a dupe of #50285, although that wasn't obvious to me at first.

@griesemer
Copy link
Contributor

griesemer commented Aug 2, 2023

Just to be clear, I am not arguing that this is a bad idea or we shouldn't do this.
I am just saying that we are not set up for it and the amount of work to make it happen in full generality may require significant (re-)engineering.

Leaving open despite the duplicate as it shows different aspects of the same problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
generics Issue is related to generics NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Proposal TypeInference Issue is related to generic type inference
Projects
Status: Incoming
Development

No branches or pull requests

4 participants