As I said, I don't really understand why we disagree here - or what we are even disagreeing about. So let me make my claim as precise as possible, in the hope that it at least helps me understand which particular part you are disagreeing with. I claim:
a) w.l.o.g. we can ignore operators (including index-expressions/range/select/…) for now, with the understanding that any program using operators on a type-parameter to a generic function can be transformed into an equivalent program using methods instead. In practice this is a bit hairy and as discussed ad nausea the obvious interface-definitions are not equivalent to the operators (given that they allow different sets of types), but for the semantic of a specific program that doesn't matter. b) Any program using functions with type-parameters can be transformed into an equivalent program without such functions. The simplest way to do that transformation is to replace a generic function func F(type A, R someContract) (a A) R { … } by (assuming A, R are not used in the package - otherwise rename) type A = interface { /* methods that someContract requires of A */ } type R = interface { /* methods that someContract requires of R */ } func F(a A) R { … } and by replacing any concrete call var a CA var r CR = F(CA, CR)(a) with var a CA var r CR = F(a).(CR) (and similarly for passing around a fully instantiated version of F) c) Given a function func F(a A) R { … } with A, R interface types and concrete types CA, CR implementing A and R respectively and assuming every return statement of F can be proven to return a value of dynamic type CR, we can create a devirtualized version of F with the signature func CF(CA) CR such that for any a, the expression `F(a).(CR)` is equivalent to `CF(a)` and the expression `F(a)` is equivalent to `R(CF(a))`. (and similarly for the func value CF when passing it around) d) Any replacement resulting from the transformation in b) fulfills the requirement for c) - that is, the type-parameters get turned into interface-types and it is provable that any return statement has a consistent static (and hence dynamic) type CR. This is guaranteed by the type-checker for generic functions. e) Thus, following the transformation for b) with the transformation for c) (restricted to the image of b) is equivalent to full template-style expansion of all generic functions for all possible sets of static types they are instantiated with in a given program. Now, to me, all of a) to e) seem fairly clear. There are bits and pieces that need to be expanded to work around details of scoping and naming and everything and a full formal proof is hard, given a lack of a formal type system for Go. But overall, this seems fair to me. If there is any specific step you disagree with, that would be helpful to understand why we can't agree. Now, with that, we'd have a reduction proof that generics can't actually perform better than the equivalent program using interfaces. The algorithm to determine if devirtualization is possible (in c) is equivalent to the type-checker of generic functions. And an algorithm that, given a list of generic functions and a list of instantiations and calls to that function, decides which of these should get a specialized version generated, can *also* take a list of candidate-functions for devirtualization and calls to those functions and decide which of these functions get devirtualized. Note, that I'm not saying *any* function using interfaces can be devirtualized - for my claim, it's only necessary that every function in the image of transformation b) can be devirtualized. This is why I'm so confused by your examples - you keep constructing functions with interface arguments that are not in the image of b), saying that they can't be devirtualized or that the question of whether they can is undecidable. But that doesn't actually matter. Worst case, we have to be able to determine whether a function taking interfaces is in the image of b), which I think is pretty easy - we basically only need to determine whether, for any interface-type returned, the static type in the return statement is consistent (for arguments, we can get around that by renaming if need be). However, I'm also saying that if we use this view and instead of implementing a heuristic for "are parametric functions compiled instantiated or generic" we view these steps separately and first implement generics completely via virtual calls (interfaces) and then implement a heuristic for devirtualization, we get a higher payoff. Because in practice there *will* be some function that would benefit from that heuristic that are *not* parametric functions. A good example is `fmt.Fprint*`, which would definitely be considered a candidate for devirtualization, as it *could* be in the image of b). i.e. by doing the two-step thing, we essentially answer the question of "when to use contracts and when to use interfaces": `func Fprint(io.Reader, ...interface{})` is equivalent semantically to `func Fprint(type R io.Reader) (R, ...interface{})` - but under this view, it also generates the same code. So there no longer is an advantage to use type-parameters except where needed (e.g. for consistency in Max) and the answer truly becomes "if you can, use interfaces". The last possibly helpful illustration of what I'm trying to argue is, that all of this is *essentially* simply the "dual implementation principle". The dual implementation principle states, that it should be possible to compile any parametric function either by compile-time or by run-time substitution. That's exactly what I'm saying: The transformation in b) is *exactly* the run-time substitution extreme in the implementation spectrum. And full template-expansion in e) is *exactly* the compile-time substitution extreme in the implementation spectrum. And thus, devirtualization is exactly the "compiler optimization" answer to the generics dilemma. On Fri, Oct 19, 2018 at 1:22 PM roger peppe <rogpe...@gmail.com> wrote: > On Fri, 19 Oct 2018 at 12:04, roger peppe <rogpe...@gmail.com> wrote: > >> The algorithm to do it is quite straightforward: >> https://play.golang.org/p/subnLkSSxdI >> > > On reflection, that ParamType hack won't work correctly with > types.Identical. This should work better: > https://play.golang.org/p/iswgf7mr8ht > >> -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.