-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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: generic should infer type from variable definition #50285
Comments
I suppose this would be a little bit like way that the language handles untyped constants, in that the type of the function would be inferred based on how the function call expression is used. But there would be a big difference, which is that untyped constants have a default type, but here the "default type" would be a compiler error. I'm concerned that the rules for when the type is inferred would be unclear to the person reading the program. People already get confused about untyped constants. In any case putting this on hold for later consideration after we have more experience with generics. |
And generic parameter should can be infer lazily. m := NewHashMap[int, int]()
m.Put(1, 2) can write as: m := NewHashMap() // currently type parameter is not set.
m.Put(1, 2) // type parameter set to (int, int) |
@leaxoy That seems very different to me, and should be a separate issue, not this one. Thanks. |
While this is on hold, I'd like to give my two cents on this one: |
For the following func f[T any]() (x T) { return }
func g[T any](x ...T) { return } On the call side, because the current type inference is only limited based on function arguments, we can infer g(f[int]()) // OK But not the vise versa: g[int](f()) // ERROR: cannot infer T This seems OK initially since it applies the philosophy of "there is only one way of doing something". However, whenever we trying to do something with g(f[int](), f[int](), f[int](), f[int](), f[int]()) // OK
g[int](f(), f(), f(), f(), f()) // ERROR: cannot infer T One must write the first style rather than the second. Was there a particular example (similar to other known limitations caused by particular examples) to prevent this during the design cycle of 1.18's generics? |
My experience so far with type inference is that the rule is: "Sometimes it works and I'm happy. Sometimes it doesn't work, and I sigh and write out the type." In any case, the specific case given in the OP is quite clear cut: the type is explicitly named as part of the declaration. Another quite clear cut case (which is the one that brought me here today) is struct literals, where the return type can be inferred from the struct field type. I suspect that listing and handling just the "obvious" cases would provide a lot of ergonomic support, without complicating the actual rules too much. |
Some simple wrapper types, such as type Optional[T any] struct {
v T
ok bool
}
func Some[T any](v T) Optional[T] { return Optional[T]{v: v, ok: true} }
func None[T any]() Optional[T] { return Optional[T]{} }
func Example(cond bool) Optional[string] {
if !cond {
return None[string]() // Not the most ergonomic.
}
return Some(someString()) // Much nicer.
} It would be a very nice improvement to be able to just do |
How far would this go? How about this?
I suspect that something along the lines of the inference proposed for function literals here would probably be reasonable. But even then, things get tricky I think:
Not unexpected, I guess, but the algorithm isn't going to be trivial. |
I don't think that's necessary, at least not right now. It could certainly be considered later, but it seems like a lot of complication for a much smaller benefit. I don't know for sure how complicated it would really be, though. It may be possible for the type checker to introduce a temporary type that it can slot in for
This seems less complicated to me than the previous example. The usage of As an aside, the |
It's definitely viable (Rust does this kind of thing) but not necessarily desirable.
Yeah, I think it should work (if type inference by LHS type is to work), but specifying the rules might not be that straightforward.
Good catch. Fixed by editing. |
I think a single statement is a good natural boundary. So return None() would work because x := None()
return x would fail because there's not enough information on the |
I think what's certain here is that for an inferred type, it would have to coalesce into a concrete type before it can be used in a concrete way. You pass generically-constrained param to a function that isn't generic, it's a compile-fail. You pass it to a function that is generic, but is constrained differently than the param, it's a compile-fail. Meanwhile, I agree with the logic on the hold. I do want this long-term, because I DO GET generics, but I also know that this is a common point of confusion. For now, we all have to write adapter handlers/callbacks, which are generic over the structured types which are part of the interface. That's good enough for me. Until generics permit access via common symbols (e.g. array index/struct-fields), which is NOT part of this proposal. we'll still be doing that anyway. |
Here's an attempt at a more concise formulation of this proposal, at least as how I understand it: If a generic function is called and the result is used in an assignment (*) to a typed variable, the type of the variable is used for type inference if the function's result type depends on a type parameter. If a function returns multiple results, the same applies for each result. (*) For the purpose of this proposal, initialization expressions (to fully typed variables) in variable declarations, assignments to redeclared (and thus fully typed) variables in short variable declarations, returning results to function result parameters, and passing values to (user-defined) functions in function calls are all considered assignments where this form of type inference will be applicable. This form of inference can be viewed as a generalization of #59338: instead of considering the type of the function, we consider the type(s) of the function results for inference. Some examples: func f[P any]() P { ... }
var x int = f() // ok, infers P = int (type of x is int)
x = f() // ok, also infers P = int (type of x is int)
var y = f() // not ok, y doesn't have a type yet, so P cannot be inferred
func g[P, Q any]() (P, Q) { ... }
var u, v float64 = g() // ok, infers P = Q = float64
x, v = g() // ok, infers P = int, Q = float64
z, v := g[int] // ok, P = int (provided), z is int, Q = float64, inferred from v
a, b := g[int] // not ok, b doesn't have a type yet, so Q cannot be inferred
func h[T any](x, y T) { ... }
h(g[string]()) // ok, T = string, inferred from 1st result of g, Q = string, inferred from y which has type T = string The |
Removing this from hold as it is a natural extension of #59338. Also, for better understanding we have prototyped the implementation for simple forms such as the For an idea of what's possible in the prototype see |
One more: Sending the result to a channel. |
I guess that should be |
@DeedleFake For now I think we may want to constrain this to a limited set of "assignments". Once we add sending results to a channel, we (probably) also need to add setting a map element or providing a map key. Then the question is what about indexing (do we infer an |
The primary issue with this proposal is that in general, type-inference is not easily localized anymore to a single ("flat") assignment/function call. Consider: func f[P any](x P) P { return x }
var x float64 = f(f(f(0))) The return type In general, this nesting can be arbitrarily deep, with many different generic functions at various instantiation levels. While type inference should be able to infer types in cases like these, our current type checkers are not organized in a way that makes such an approach easily feasible: type checking essentially happens recursively, bottom-up. To make this more general inference work, expressions will need to be type-checked "as a whole" which likely will require significant re-engineering of the type checker. Putting back on proposal-hold for now. |
[#58650] solved the problem, should close now |
Solved? |
Is this issue really "solved"? My understanding was that #58650 does not implement this; it just makes this easier to implement. Also, a small modification of the code from the top of this issue does not compile in the playground, not even with the version set to "Go dev branch". https://go.dev/play/p/ry-w07CKoct?v=gotip |
This issue is not solved. See this comment for details. Leaving on hold. |
I meet the same problem.
|
hope go could support infer generic type from variable type
The text was updated successfully, but these errors were encountered: