-
Notifications
You must be signed in to change notification settings - Fork 17.5k
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: allow type parameters in methods #49085
Comments
The document also explains what the problems are. So what are your solutions to these? |
This proposal is good to define an |
This proposal is a non-starter unless someone can explain how to implement it. |
@ianlancetaylor from the generics proposal
I think this solution makes the most sense. They could then (under the hood) be treated a regular function. The reason why this would be useful is that methods do not only serve the purpose of implementing interfaces; methods also serve as a means of organization for functions that operate on particular structures. It may be a bit of a challenge about how type-parameterized methods would appear in |
The problem would be simpler if the parameter type possibility set is known at compile time,
One new problem I'm aware of is there might be many methods have the same name for a certain type. |
If we're ultimately talking about multiple dispatch, the languages that really cater towards this do a massive amount of overloading. One language I find fun and interesting with this style is Julia, where things like Particularly for stream-to-stream conversion, I do think that the List Transform example is useful. We have to provide a concrete I think it's also often possible to have a higher-order function that generates conversion functions, while more specialized conversion functions are also naturally expressible in Go. Example: a very generic color conversion API might specify an interface with |
Even just that would allow for, for example, an iterator implementation, though it does require wrapping it in another type because of the lack of extension functions: type Nexter[T any] interface {
Next() (T, bool)
}
type NextFunc[T any] func() (T, bool)
func (n NextFunc[T]) Next() (T, bool) {
return n()
}
type Iter[T any, N Nexter[T]] struct {
next N
}
func New[T any, N Nexter[T]](next) Iter[T, N] {
return Iter[T, N]{next: next}
}
func (iter Iter[T, N]) Map[R any](f func(T) R) Iter[R, NextFunc[R]] {
return New(NextFunc[R](func() (r R, ok bool) {
v, ok := iter.next.Next()
if !ok {
return r, false
}
return f(v), true
})
}
// And so on. Usage is still awkward without a short-form function literal syntax, though, unfortunately: s := someIter.Filter(func(v int) bool { return v > 0 }).Map(func(v int) string { return strconv.FormatInt(v, 10) }).Slice()
// vs.
s := someIter.Filter(func(v) -> v > 0).Map(func(v) -> strconv.FormatInt(v, 10)).Slice() |
This will add so much complexity |
@batara666 can you explain why? adding type parameters to methods doesn’t seem like it’d add that much complexity to me. |
I think before we can think about if and how to do this, we should first address the "no higher level abstraction" restriction of generics, i.e. the inability to pass around a generic type/function without instantiation. The reason is that if we allowed additional type parameters on methods, we'd also de-facto allow to pass around generic functions without instantiation: type F struct{}
func (F) Call[T any] (v T) { /* … */ }
func main() {
var f F // f is now de-facto an uninstantiated func[T any](T)
} Therefore, to allow additional type-parameters on methods, we also have to answer how to pass around uninstantiated generic functions. Moreover, if we'd allow passing around uninstantiated generic types/functions, we could already build the abstractions given as motivations in the proposal-text. So, given that solving "no higher level abstraction" is a strictly easier problem to solve, while providing most of the benefit of solving "no additional type parameters on methods", it seems reasonable to block the latter on solving the former. Lastly, I'd urge everyone to consider that these limitations where not left in the generics design by accident. If they where really that easy to solve, the solution would have been in the design to begin with. It will take some time to solve them. |
The "type" concept almost means a memory layout. A value of a subset type could be passed/assigned to a superset type. For Go, the change would be too large. |
I was (far too) pleased (with myself) when I figured out this is possible: https://gotipplay.golang.org/p/1ixYAwxwVss The part about lifting values into type system 'symbols' feels like a bit DIY and tricky, not sure there isn't something better here. I did feel like the dispatch() call is technically interesting. Inferring from the function/method supplied to dispatch() wasn't obvious to me at first. Without overloading methods, just different instantiations of the dispatch() function, it is plausible to arrive at the correct dispatch over a set of concrete implementations. |
I'm putting this proposal on hold until we have more familiarity with the current generics implementation. |
Playing with a new library that intensively uses generics, I provided an equivalence implementation between a Method and a Function: At the end, the difference is only where the parameter is placed (as a receiver, or as a first argument), but the function allows you map to a Stream of different type and with the method you can only generate streams of the same type. With the code from the above link, I verified that this compiles: type Mapper[IT, OT any] func(Stream[IT], func(IT)OT) Stream[OT]
var _ Mapper[int, float64] = Map[int, float64] Simplifying, and obviating some internals from Go that I might ignore, I could see the generic In order to overcome the No parametrized methods issue pointed by @fzipp, from my partial view, I think that the example issue can be approached the same way as Java does: using func CheckIdentity(v interface{}) {
if vi, ok := v.(p2.HasIdentity); ok {
if got := vi.Identity[int](0); got != 0 {
panic(got)
}
} Would be translated to something equivalent to: func CheckIdentity(v interface{}) {
if vi, ok := v.(p2.HasIdentity); ok {
if got := vi.Identity(0).(int); got != 0 {
panic(got)
}
} Then the third line would panic if the In this case, we are translating the error check from the compile time to the runtime, but anyway this is what we actually have now if lack of parametrized methods forces us to continue using unsafe type castings. |
In your rewrite of |
This is a bad idea in my opinion - Type erasure is one of the most annoying limitations of generics in Java. I think it would go against the grain of the simplicity that Go aims for. |
I don't really have a horse in this race, but I find this proposal interesting and wanted to put down my thoughts. Based on what I see here: https://go.godbolt.org/z/1fz9s5W8x func (x *SomeType) Blah() { /* ... */ } And this: func Blah(x *SomeType) { /* ... */ } compile to nearly identical code. If we have a type type S struct {
/* ... */
} ...and func (s S) DoThing[T any](arg T) { /* ... */ } ...then we effectively have a generic function with the signature: func DoThing[T any](s S, arg T) { /* ... */ } Of course, if we have a generic type type G[T any] struct {
/* ... */
} ...and func (g G[T]) DoStuff[U any](arg U) { /* ... */ } ...then we effectively have a generic function with the signature: func DoStuff[T, U any](g G[T], arg U) { /* ... */ } In order to use either of these "functions", all of the type parameters have to be known. That means that in the case of Within this limited context, it seems to me like it wouldn't be a huge leap to allow type parameters on methods - it ends up referring to what is essentially a generic function, and we know things about generic functions:
The mechanism by which this interacts with interface definitions/implementations is less clear to me. Though, I think it is reasonable to say that a generic interface can't be implemented directly - it must be instantiated first. I'm not as sure of this, but it seems that it might also be true that an interface can only be implemented by a fully instantiated type. Even in code like this: type GenericInterface[T any] interface {
Foo() T
}
type GenericStruct[T any] struct {
Bar T
}
func (g GenericStruct[T]) Foo() T {
return g.Bar
}
func MakeGeneric[U any]() GenericInterface[U] {
return GenericStruct[U]{}
} It seems like, within the context of One area that seems complex is interfaces whose methods have type parameters. For example, if we had: type Mappable[T any] interface {
Map[U any](func(T) U) []U
} What would it mean to "instantiate" It seems much simpler to disallow that kind of interface entirely, and just require something like: type Mappable[T, U any] interface {
Map(func(T) U) []U
} I think that could still be just as useful, depending on how interface implementation is handled when the underlying type has methods with generic parameters. As an example: // Slice[T] provides slice operations over a slice of T values
type Slice[T any] []T
// Map[U] maps a Slice[T] to a Slice[U]
func (s Slice[T]) Map[U any](func (T) U) Slice[U]
type Mappable[T, U any] {
Map(func (T) U) Slice[U]
}
// In order for this assignment to be valid:
// 1. Slice[int] must have a method named Map ✅
// 2. Slice[int].Map must have the same number of arguments ✅
// 3. Slice[int].Map must have the same number of returns ✅
// 4. Slice[int].Map must have the same types for each argument and return ???
var _ Mappable[int, float64] = Slice[int]{1,2,3} It seems reasonable to me to say that In this case, assuming that methods with type parameters are allowed, I would think the compiler could do something like:
If you're calling the method an the interface object, then you only have access to that one particular instantiation of To summarize: Given that it is a feature of go that interface implementation can be tested at runtime via type assertions, reflection, etc, I don't see any way around banning generic methods on interface definitions. However, because methods are more or less sugar for functions, it seems to me it would be possible to allow generic parameters on methods of concrete types, and to allow these methods to participate in implementation of fully instantiated interface types. |
I don't think you are solving the problems from the design doc, though:
This fulfills all your criteria, it uses no parameterized interfaces and It is very easy to look at the proposal text and think "this would be a useful feature to have, I obviously would like it in the language". But because it's such an obvious feature to put in, it would be great if people ask themselves why the Go team didn't put it in in the first place. Because there are reasons and these reasons need answering. |
I would like to drop an idea here which I think can be useful for the "type parameters in methods" topic. Maybe it has an obvious flaw I haven't seen or it has already been considered or discussed, but I couldn't find anything about it. Please, let me know if so. With the current proposal, we can't have type parameters in methods but, couldn't we achieve the same effect if we put the type parameters in the type definition? I mean, instead of doing this: type Slice[T any] []T
func (s Slice[T]) Map[U any](func (T) U) Slice[U] Do this (move the U type parameter from the method "Map" to the struct): type Slice[T any, U any] []T
func (s Slice[T,U]) Map(func (T) U) Slice[U] @Merovius Wouldn't this solve the issue you mentioned in the above comment? Your example would end up like this: type IntFooer interface { Foo() int }
type StringFooer interface { Foo() string }
type X[T any] struct{}
func (X) Foo() T { return *new(T) }
func main() {
var x X[int] // You are forced to specify the type parameter here with the current proposal. I guess it could be inferred it this were an initialization instead of a declaration only
x.(StringFooer) // This would fail, as it doesn't conform to the interface
x.(IntFooer) // This would pass
reflect.ValueOf(x).MethodByName("Foo").Type() // "Foo(X) int"
} I think this would work with the current proposal without changes. As I said, I could be missing something obvious here. Let me know if that's the case. |
You can easily do this, but it's not the same effect. People who want this specifically want the type-parameter of the method to be independent of the type itself, to implement higher-level abstractions. |
I see, thanks. After thinking it twice, I now see that what I propose would be very limiting as, after instantiating the type, you could not call the method with different type parameters (for example, call "map" with a function that return strings one time and then another time with a function that return ints). All right, I know there was something obvious here. Thanks for the response! |
I'm sure that's true, I probably would benefit from reviewing it again. To be fair, though, I wasn't trying to put together a concrete proposal - I understand why this is a complex topic and why it isn't in the first pass at generics, and why it may never be added to the language. I don't have a horse in this race beyond the fact that I find this interesting. My intent was to think out loud about what restrictions might make this more concretely approachable. Also, for what it's worth, I think that disallowing parameterized methods on interface types could be seen to addresses some of the problems put forth in the proposal.
I do not disagree. I feel as if you may have misinterpreted my intent. I am not saying "this is so easy, look at how we can do it" - I am saying "here is an interesting constraint that might make this more approachable, and which could perhaps be used as the basis for additional discussion" I'm willing to brainstorm this, but again I am not proposing a concrete solution as much as attempting to provide a possible set of constraints for discussion. If that exercise shows that thia feature would too complex, that is a totally acceptable outcome in my opinion. Obviously we cannot use runtime code generation, I don't recall proposing that nor do I think it is necessitated by anything said above. Given that, here are some possible (not exhaustive or comprehensive) directions the compiler could choose: For
For
This is exactly what I attempted to do here. I wrote this at 1am on the last legs of a cup of coffee, and it seems I failed to consider some scenarios in my comment. A simple "How would this address X and Y" would have accomplished the same effect without this lecture at the end. |
To be clear, this is what the proposal says about this question:
The solution you suggest seem a variation on the second option here. |
And how having to either pass necessary parameters like logger (if not using a singleton or stdlib logger), database connection etc or a pointer to the method's receiver which contains these to generic functions when using dependency injection help consistency and simplicity? I.e. instead of being able to write something like package otherpackage
type SomeService struct {
db *pgxpool.Pool
log *zerolog.Logger
}
func (s *SomeService) Foo[T Bar](ctx context.Context) (T, error){
// ...
} One needs to write a generic function like this: func Foo[T Bar](ctx context.Context, s *SomeService) (T, error) Then let's say we have another module of our application, where Let Let's say But a function using type constraints (we'll use the one from previous example) currently has to be referenced as |
ad-hoc polymorphism doesn't only means function overloading, it means the behaviour of code relates to type. for example
in the above code, var t T and t.Foo() all depents on the actual type of T. But parametric polymorphism, it will not depends on the type of T(where t.Foo() will converts to subtyping polymorphism, but you need to provide a instance as parameter, that is func[T any](t T)). This kind of program exists in the form of parametric polymorphism but it has adhoc aspects, so actually they are adhoc polymorphism. You might argue that adhoc aspects should refer to the kinds like template specilization, but of course Golang has another typical adhoc polymorphism:
I'm not here to argue on this subject, but I'm here for a new proposal, and I apologize for I didn't go thought the whole thread to see if someone has the same proposal before. The idea is just make program can't apply type argument to a interface type, but only can apply to a actual type with interface constraint. For example
I think the whole program doesn't need much more changes for the current go compiler, and it doesn't need any changes in the runtime representation at all. And it has forward compatibility if we can find other ways to support Foobar0, Foobar1 and Foobar2. |
@nyanpassu ad-hoc, parametric and subtype polymorphism have established definitions that are incompatible with yours. It is confusing that you are using jargon in a way that is incompatible with established definitions.
There is always time to rectify that.
I agree that what you suggest could be implemented within the constraints set forth for the generics proposal. But it has the same problem other, similar suggestions have: It essentially implies that generic methods do not participate in interface-satisfaction. That is, from what I understand, under your rules, this program would panic: type S struct{}
func (S) M[T any](T) {}
type J interface {
M(int)
}
func main() {
var x any = S{}
x.(J) // panics
} This is one of the issues brought up in the design doc to disqualify similar designs. And I'll note that if you only allow statically dispatched generic method calls, you can always use a top-level function as well. That is, if you know the type statically, the package it's defined in can also provide a package-scoped function for you to call. From the design doc:
|
Emmm, firstly, type class is a way to support adhoc polymorphism in type system, and using type bound for statical dispatch is not far away from that. And if you check the definition of adhoc polymorphism, you will find it also refers to such kind of codes which also exists in Golang void foobar(x Object) {
if (x instanceof Integer) {
...
}
if (x instanceof Number) {
...
}
} Yes I mean the program will panic. (I have edit this post, the previous one is wrong. sorry I'm a little confused by myself somehow, maybe because it's late night in my timezone).
I think it's acceptable. And actually S::M has type And one more thing is that we will not be able to use interface object in such condition:
This might be strange for some users but it's acceptable. And actually it's the same way that Rust used to imple dyn objects(which is similar to Go interface), but they didn't allow any dyn objects from generation if the definition contains type specific behaviour. However it's practical that we generate runtime object which only has part of the definition of the interface. We can't always use a top-level function, because I need polymorphism in this case:
I need the implementation of scope can be changed. If I can only use top-level function, I can't write program simply in this way. Without rank-n types, it's really hard to implement generic reactive library in Golang. |
That is fine. Many people in this thread have come up with designs they personally find acceptable. But that is not the decision criterion for inclusion into Go. As long as the people who make that decision do not find it acceptable, it is probably most helpful to try and understand why. |
I have explain my reason for that panic is acceptable is because a type
is not the same as the type
Because the previous one has quantification on the method. It's just like the same question that people will ask why no covariance/contravariance in Golang
|
Yes. Don't worry, I understand that you find your solution acceptable and I understand your reasoning for that. Did you understand mine? About it being undesirable to have generic methods, if they can't be used in interfaces?
To be clear: The decision was to not require either.
Yes. I would prefer Go to have Co- and Contravariance for functions and methods. But it's not up to me, the Go team disagrees and I can accept that. |
Sorry, about Currently we can't use interface contains type set as a runtime object but only as type constraint, so I guess what you mean is not that interface with generic method must can be used as runtime objects, because currently we can't use every interface as runtime objects.
Because type constraint only lives at compile time, so we don't need to worry about that we can't use generic method in type constraint, just make interface with generic method can't be used as a type. But indeed Golang has a plan to support using type set as union type at runtime. So maybe you indeed mean that if Golang is going to support generic method, then interface with generic method must can be used as a runtime objects. For a function of the type like
Let discuss them seperately. For the first one, we must have unique implementation for every type at runtime. Using this approach means type erasure which many people had discussed about it and were denied. My very first suggestion is only add a keyword to mark it explicitly so coders knows the abstraction behind it(I don't know whether it's the cost of abstraction making it denied or the abstraction itself). For the second one, either we must collect every potential type application at compile time, including the following one:
We must treat HasIntIdentity::Identity a type application of HasIdentity::Identity[int], which will add too much complexity to the compiler. Or we can introduce JIT compiler, which I think was also discussed in this thread is not worthy to do. So my final thought is that, we can use interface with Generics as type constraint only just like type set now. And if there is further option to use it as runtime objects, at that time we can try to use it as runtime objects just like the option to use type set as sum type. It's fully forward compatible.
I've gone throught the whole thread roughly and I didn't find similar suggestions, and I apologize if there are any(as it's so simple someone must have the same idea before). |
This comment was marked as duplicate.
This comment was marked as duplicate.
Hi everyone. Let me add something from my self too. This topic however produced so many posts that it is really impossible to read them all to know the whole context so forgive if I miss something. I'd like to rfer the very beginning of this conversation where it is said that:
Let me mentioned code herepackage p1
// S is a type with a parameterized method Identity.
type S struct{}
// Identity is a simple identity method that works for any type.
func (S) Identity[T any](v T) T { return v }
package p2
// HasIdentity is an interface that matches any type with a
// parameterized Identity method.
type HasIdentity interface {
Identity[T any](T) T
}
package p3
import "p2"
// CheckIdentity checks the Identity method if it exists.
// Note that although this function calls a parameterized method,
// this function is not itself parameterized.
func CheckIdentity(v interface{}) {
if vi, ok := v.(p2.HasIdentity); ok {
if got := vi.Identity[int](0); got != 0 {
panic(got)
}
}
}
package p4
import (
"p1"
"p3"
)
// CheckSIdentity passes an S value to CheckIdentity.
func CheckSIdentity() {
p3.CheckIdentity(p1.S{})
} I think the argument here is irreleventNow I'm reading the justification below the code:
and it doesn't make sense for me at all! the fact that "p3 does not know anything about the tpe p1.S" is what the interface is... **Edit1** here is an examplepackage main
import "fmt"
// package p1
type S struct{}
func (s *S) N(v int) int {
return v
}
// package p2
type U interface {
N(int) int
}
// package p3
func UseU(v interface{}) {
if vi, ok := v.(U); ok {
fmt.Println(vi.N(42))
}
}
// package p4
func main() {
UseU(&S{})
} Alternative solutionIf you really don't know how to implement that interface stuff , I'd suggest to allow such a methods but diallow casting them into an interface. Let me know if I missed something. |
The crux of the problem is that with a parametric method, the generated code for the body depends on the type-parameter (e.g. addition on integers requires different CPU instructions than addition of floating point numbers). So for a generic method call to succeed, you either need to know all combinations of type-arguments when compiling the body (so that you can generate all bodies in advance), or you need to know all possible concrete types that a dynamic call could get dispatched to, when compiling the call (so you can generate the bodies on demand). The example from the design doc breaks that: The method call is via an interface, so it can dispatch to many concrete types and the concrete type is passed as an interface, so the compiler doesn't know the call sites. With your example, this is not a problem, because the method body involves only a single concrete type. So when the compiler encounters the method, it just generates code for that and any method call can only ever refer to that one body.
That has been discussed and it seems that if we can't use generic methods to implement interfaces, that the cost of adding them (which then includes the confusion that some methods can't implement interfaces) is not worth the benefit. You might well disagree with that statement and feel that some of the alternatives are worth it. But Go is, at the end of the day, governed by the Go team, which decide these matters based on their own experience and preference. Personally, I like it that way, because I think that having a somewhat consistent vision is good for a project such as this. You might disagree with that as well, in which case the most fundamental software-freedom is the freedom to fork. |
Generic function can realise Map function, but it is not easy to achieve chain invoke, so we just need a little syntax sugar to make function invoke like method invoke, don't need to care about interface and so on, just handle it at compile time, like:
|
generic methods are NOT just about chain invoke. They are also about how you abstract your program(especially with interface) and reduce software complexity by generic constraints. |
Interesting to note that this problem is linked to one of the very first decisions in Go, namely whether |
Fwiw, here's one thing I'd like to do if I had type parameters, note the second method which does not depend on any type parameter of func (s *Settings) String(key string) (string, error) {
if s == nil {
return "", nil
}
return settings.String(s.Key + key)
}
func (s *Settings) StringAs[T encoding.TextUnmarshaler](key string) (T, error) {
var t T
s, err := settings.String(key)
if err == nil {
err = t.UnmarshalText([]byte(s))
}
return t, err
} |
@andig Why not func (s *Settings) UnmarshalInto(key string, p encoding.TextUnmarshaler) error {
v, err := s.String(key)
if err != nil {
return err
}
return p.UnmarshalText([]byte(v))
} It's a little bit more to type at the call-site, but I don't see why you need generics at all here. off-topic, but I'll also note that your example probably wouldn't work, you'd need type TextUnmarshalPointer[T any] interface {
*T
encoding.TextUnmarshaler
}
func (s *Settings) StringAs[T any, PT TextUnmarshalPointer[T]](key string) (T, error) {
var t T
s, err := settings.String(key)
if err == nil {
err = PT(&t).UnmarshalText([]byte(s))
}
return t, err
} |
Whoever is going to call a method by user input string? And if there is someone, just don't allow to call parametrized methods (panic, for example) as it is not instantiated. That would be fair and logical.
Why instantiate |
Anyone using text/template. |
Oh, didn't know. |
(@ianlancetaylor points out that my suggestion below is substantially similar to the one proposed by @jpap in #49085 (comment). I post it here in case it sheds light on the state of the problem or the details of the technical challenges.)
If there were no interfaces, this feature would be straightforward to implement. The problem: generic methods mean that the method set of a type is potentially infinite, and the code for all the methods is not generated unless it is needed. For a concrete type C, we cannot know at build time whether code for a given method For now let's restrict ourselves to interfaces without generic methods, as this still gives us the expressiveness benefits described above. (Is this a reasonable restriction? I haven't yet thought about what it would take to relax it, or even whether it is fundamentally a harder problem.) Consider: type C struct{}
func (C) f[T any](T) {}
type I interface { f(int) }
var x any = C{}
i := x.(I)
i.f(0) For security reasons, Go doesn't use run-time code generation, and we don't want to change that. But we could compile each generic method (Obviously this would be less efficient than the expanded code we would get for C.f[int] or any other specific instance known at build time. In particular, non-escaping locals would have to be heap allocated since their size is not statically known. I think Ian's original generics proposal years back described something like this as a possible implementation strategy of generics in general.) Now consider the implementation of the x.(I) operation. It must query the itable of x, which is C-as-any, for a method f(int). Let's assume this method was not created at build time, so it is not found. The operation would then look at the types (C, I) and deduce that C can implement I so long as method C.f[T] is instantiated at T=int. So, for each such generic method, we obtain a descriptor such as T=int. The function that is then inserted into the itable for C-as-I would be a generic (assembly) adaptor function that is responsible for turning a call to I.f(int) into a call to the general C.f[T] implementation, passing the Since one adaptor function must work for all types, it's clear that just a method address in the itable is not enough: the itable needs to be a set of pairs of (method address, descriptor), and this means that the calling convention for interface methods would need to change. Instead of simply indexing the itable and CALLing the address, the call would need to add the method type descriptor found in the itable to the end of the argument list before the CALL. This means all methods (generic or not) called via an itable would get an extra argument, whether they like it or not, which they would ignore. (Only the special adaptor needs to look at the descriptor.) The compiler would need to add this (hidden, ignored) parameter to all methods, but since it is ignored, it should be safe to continue to use the method as a func value in a method expression such as _ = Type.method. Obviously this scheme has a number of serious limitations and costs:
Any one of these (the first three in particular) may be prohibitive. However, I think it would be a correct implementation of |
Since I was @, allow me a few brief comments:
What about
The set is only unbounded when you consider reflection. Otherwise you could bound the types in the way I had previously proposed, using a pre-link collation and instantiation step. That was also rejected outright, citing elongated build times and toolchain implementation complexity; though I'm sure there'd be clever ways to reduce the build time, perhaps with the help of the build cache which is already extensively relied upon by the toolchain.
The reason code generation was previously deemed undesirable was because of the runtime overhead, implementation complexity, and longer time-to-first-run of a JIT compiled method compared to its non-JIT cousins. Of course, that's not even an option on some sandboxed platforms.
But why wasn't it instantiated at build time? ;) At build time, we could determine that:
Note that this procedure may instantiate some generic methods that are ultimately not used by the program. That's the bound I reference above. Footnote: I previously proposed that it would be far simpler to just disallow generic method instantiations at runtime via reflect, for those method types that are not already instantiated at compile time, that already exist in the binary. That is, declare generic methods not instantiated at build time as unavailable (an error condition) at runtime. Perhaps we just need to think about reflection not just about "type reflection", but as a reflection of the program structure that is baked into the binary at compile time. 🤷♂️ |
Plugins add new types to the program dynamically, defeating any sort of whole-program analysis. Beyond that, how does this procedure deal with these situations?
|
According to the Type parameters proposal, it is not allowed to define type parameters in methods.
This limitation prevents to define functional-like stream processing primitives, e.g.:
While I agree that these functional streams might be unefficient and Go is not designed to cover this kind of use cases, I would like to emphasize that Go adoption in stream processing pipelines (e.g. Kafka) is a fact. Allowing type parameters in methods would allow constructing DSLs that would greatly simplify some existing use cases.
Other potential use cases that would benefit from type paremeters in methods:
Assert(actual).ToBe(expected)
On(obj.Sum).WithArgs(7, 8).ThenReturn(15)
Edited by @ianlancetaylor to add: for a summary of why this has not been approved, please see https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#no-parameterized-methods .
The text was updated successfully, but these errors were encountered: