Thanks very much for the feedback, Axel. I didn't think it was harsh, this is just the kind of information I needed before burning needless effort on the idea. Getting started with these conversations is quite intimidating because there is so much information and history, it's hard to find what is relevant, let alone digest it all. I appreciate that anyone spends the time to help a newbie.
Just for interest's sake, I wanted to respond to some of your questions. I take your point about some sections of the proposal being incomplete or too sparse, but I don't think there's any point in spending time on that when there are obvious problems. > I also think one thing we should be clear on, is that methods fundamentally are never "just syntactic sugar for functions" - unless you really are proposing Uniform Function Call Syntax. I don't understand this, but I do take your word for it. This is probably the "something very obvious" I missed. > In particular, your proposal has name spacing implications - it would be possible to have two methods of the same name on different interface types, while it is not possible to have two *functions* of the same name, even if they accept different interfaces as their first argument. I believe I accounted for this in the restrictions and scratched the surface of this challenge in the section about composition. However, my approach is obviously naive, I expect there's a good name for this problem and it likely has to be dealt with more systematically. > I believe it would be confusing for methods on a `MyReader` to disappear, when assigning them to an `io.Reader` and to appear, when assigning an `io.Reader` to a `MyReader`. Agreed, and it only takes passing mr to a func(io.Reader) for the MyReader method information to be lost at compile time. This points out a serious limitation on the idea of implementing through code rewriting; it could only work for locally scoped variables. > How do you deal with a situation where the *dynamic type* of an interface has that method, but not its static type? That is, what if I assign an `*os.File` to a `MyReader`, for example? What if I assign an `*os.File` to an `io.Reader` and then convert that to a `MyReader`? Not sure I see your point here. *os.File to a MyReader would be Okay. The type would gain the WriteTo method. *os.File to io.Reader, of course is okay, and then to a MyReader is also okay. The dynamic type would have only the Read and WriteTo methods. I do think the proposal of using code rewriting avoids the issue of parametrized methods, but its usefulness is completely destroyed by the limitation you pointed out. Thanks again Axel! On Wednesday, March 15, 2023 at 1:53:16 AM UTC-6 Axel Wagner wrote: > Hi, > > Some comments in-line: > > > > Who does this proposal help, and why? > > go programmers who want to reduce boiler plate code. > > I believe you should definitely be more explicit with the problem you are > trying to solve. "Today, if you want to do X, you have to write Y. That is > bad, because Z". > > There are many different kinds of boiler plate, so just saying you help > "Go programmers who want to reduce boiler plate" carries little to no > information. > > > interfaces and named aliases would become allowable receivers. The > change would just be syntactic sugar for functions that accept this > interface. > > I don't have a specific reference handy, but more or less exactly this has > been proposed a couple of times in the past and always been rejected. > So, apologies for being harsh, but I would predict that this has an > essentially zero chance of being accepted. > > I also think one thing we should be clear on, is that methods > fundamentally are never "just syntactic sugar for functions" - unless you > really are proposing Uniform Function Call Syntax. In particular, your > proposal has name spacing implications - it would be possible to have two > methods of the same name on different interface types, while it is not > possible to have two *functions* of the same name, even if they accept > different interfaces as their first argument. > > So, this is not just syntactic sugar, it carries semantic meaning. And if > it where, it would not be likely to solve the problem you are trying to > solve. > > > There are some restrictions: > > > > R1: Just like normal methods, “interface methods” can only be defined > within the package the interface is defined. This prevents imported code > from > potentially breaking or overriding the intended functionality. > > I don't think this is a good idea. Interfaces, by their nature, are mostly > package-agnostic. That is, I can define an interface > type MyReader interface{ Read(p []byte) (int, error) > and while it is not *identical* to io.Reader, it is mostly equivalent, > that is, they are mutually assignable. > > I believe it would be confusing for methods on a `MyReader` to disappear, > when assigning them to an `io.Reader` and to appear, when assigning an > `io.Reader` to a `MyReader`. > > Note also, that Go has interface type-assertions. So how would this work, > for example? > > func (r MyReader) WriteTo(w io.Writer) (int64, error) { … } > type R struct{} > func (R) Read(p []byte) (int, error) { return len(p), nil } // implements > io.Reader > func main() { > _, ok := io.Reader(R{}).(io.WriterTo) // fails > mr := MyReader(R{}) > _, ok = io.Reader(mr).(io.WriterTo) // succeeds, presumably? > ch := make(chan io.Reader, 1) > ch <- mr > (<-ch).(io.WriterTo) // fails? succeds? > } > > That is, a `MyReader` would implement `io.WriterTo`. But if it is > intermediately stored in an `io.Reader` (say, by passing it through a > channel), the compiler no longer knows if and where to find a `WriterTo` > method. > > This really is just a corollary/demonstration of why it's confusing for > methods to appear/disappear when converting them between io.Reader and > MyReader. > > > R2: interface methods cannot override existing methods. Attempting to > cast an object to an interface that it already fulfills or partially > fulfills would be > > a compile time error. This prevents interfaces from becoming > self-fulfilling, or overriding expected functionality. The compile time > error is necessary > > so future updates to packages don’t change functionality unexpectedly. > > How do you deal with a situation where the *dynamic type* of an interface > has that method, but not its static type? That is, what if I assign an > `*os.File` to a `MyReader`, for example? What if I assign an `*os.File` to > an `io.Reader` and then convert that to a `MyReader`? > > > My motivation for this feature is to mitigate limitations on dynamic > types. Consider the typical approach to handling dynamic json. > > […] > > > Access could be simpler through a hypothetical jnode package. > > […] > > I believe you can functionally achieve the same result if you make `node` > a struct type with an `UnmarshalJSON` method and an `any` or > `map[string]any` field or something like that. > > > Fortunately, there’s no need to rush into this. We could start by saying > extension methods do not contribute to the method set, and therefore do not > > help fulfill interfaces. Unfortunately, that caveat would fall to > programmers to understand, raising the bar for learning go. I believe the > ultimate > > design here would definitely be to allow the extension methods into the > type’s method set. > > Note that the resulting confusion of having methods that are not in the > method set, when considering interface satisfaction, is why we do not > have extra type parameters on methods > <https://github.com/golang/go/issues/49085>. I don't believe this > proposal has significantly more advantages than allowing that would have > (it probably has significantly fewer) so I don't think this proposal would > overcome that concern. > > > This example illustrates how the code rewriting can lead to a kind of > support for generic methods. > > > > Theoretically, this could be a starting point for how generic methods > might be possible, though there’s a lot of investigation to happen > > before that’s possible. > > I think it rather demonstrates why it runs into the same fundamental > issues that the proposal to support extra type parameters on methods has. > It seems likely to me, that if we could overcome those issues, we'd prefer > to support extra type parameters on methods, instead of this proposal. > > > > Is this change backward compatible? > > Because defining methods on interface receivers has been illegal, I > believe this is a backward compatible change. > > No existing code will be doing this. > > That depends on a couple of details - specifically questions the kinds of > questions I asked above. Depending on how you answer those, the fact that > the dynamic method set of a type can change by passing them through > interface types could break compatibility. > -- 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. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/72cacb9e-117c-4b5f-8015-1fdd7756f040n%40googlegroups.com.