-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Optional explicit interface implementations #22020
Comments
This topic was probably widely discussed during the development of golang. use comments for Code readability // PathError implement IError
struct PathError {
Error
path string
} |
"probably" seems like an easy way to dodge the effort of giving this any serious consideration.
Well, there is nothing really surprising in this thread - there are arguments made both for and against implicit and explicit interface implementations, but it doesn't seem like anyone is really arguing against having both? TypeScript gets praised here for having "good balance" by providing both - explicit interfaces when strong contracts are required, and implicit interfaces when flexibility is needed. It is true that explicit interfaces can create "temporal dependency", and yes, sometimes that is a mistake - at other times, strong coupling is what you want, because a breaking change is what you want. The Regarding premature abstraction, those aren't really the examples I'm citing - in some cases, there are well known abstractions, and you want to make sure those constraints hold. In other words, a breaking change is what you want. I'm not advocating for Java, nor for the type of large complex frameworks with tons of contracts you see in that ecosystem. Having explicit interfaces implementation as an option will not created reduced flexibility, or make it harder to adapt existing code to new use cases. It's optional - you wouldn't use it except in those cases where coupling to an interface is wanted. It won't create unnecessary coupling between packages - again, because it's optional, it's up to you how much coupling you want. Coupling isn't automatically "bad", it depends on the context - unnecessary coupling is bad, and we're seeing a lot of that in e.g. Java and C#, but I don't feel like this has been a problem in TypeScript, where both implicit and explicit interfaces are available. Personally, I rely heavily on implicit interfaces in TypeScript - in fact, I don't think the standard libraries/types for DOM/BOM/Node would be usable at all without implicit interface checks. I see a lot of solid arguments in this thread against having explicit interfaces only. But the closest the discussion came to the topic of having both, was one user mentioning TypeScript's approach, which allows optional explicit implementation of interfaces - but this was presented more as an observation rather than an argument for or against having both. If you disagree, can you point out a specific argument?
Comments aren't a good substitute for code - I would prefer to have something that can actually be checked. Comments get out of sync with the code, and often end up doing more harm than good. |
@mindplay-dk thank you for the extremely clear explanation of the benefits of that feature 🙇🏻♂️ . You are absolute right that it was proposed many times before, but afaik so far, no one has managed to provide so many convincing arguments for it. |
Personally, I am all for it, as long as it is optional, for all the stated reasons. |
Thanks for the great write-up @mindplay-dk I agree with you. |
What are the implications for the compiler? |
Yes, optional. Interfaces will be declared the same way, and nothing will change. It won't be like Java and C++, it's just a hint that the developer gives to the compiler and to the reader. |
If they remains optional and don't have too much impact on the compiler for me they are OK! :) |
The implements keyword was added earlier today in commit #0090170 |
Only took 12 minutes to implement. V compiler is amazing. |
whoa! 🤯😄 you guys needed a lot of persuasion, but dang you don't waste any time when you've decided! thank you for taking this seriously and acting so fast! that was wild. 😄 |
Yeah :) I still need to push a fix that allows implementing multiple interfaces, and removes the new keyword. A simple ident can be used here, since it's the only place it's used in. The fewer keywords, the better. Will do that today. |
Describe the feature
Just to be clear, I'm aware this has been requested before:
struct
implementsinterface
#1314But it doesn't seem like this was ever discussed at length - and no in-depth reasoning was presented, explaining why this feature should be considered, so I will try to do that.
To be clear, what I'm proposing is an optional explicit
implements
keyword, which would apply tostruct
declarations.What I'm proposing is this:
(Alternatively,
implements
could be abbreviated as:
if the terser syntax feels more like V.)The
implements
declaration would be checked locally - that is, this declaration will first validate thatMyStruct
actually implements all the fields and methods in each of theIFirst
andISecond
interfaces, before validating or reporting errors about invalid arguments in call sites, or invalid assignments, etc.Of course, we would prefer not to write explicit implementations everywhere, because it's convenient, and because it does work well most of the time - to be clear, I very much like the approach taken interfaces by Go and TypeScript, it's definitely preferable most of the time, just not all of the time. TypeScript doesn't strictly need this feature either, but it has this feature, because there are times when being clear and explicit is objectively better for clarity, as well as for error reporting.
Use Case
There are two reasons why this should be given proper consideration.
1. Code readability
This example from the documentation demonstrates a code readability problem:
The example actually highlights the problem with the comment "V automatically casts this to IError" - I understand this comment was added to the manual to explain what's happening, but it's not unthinkable someone would actually put a comment like this in their code, since it is not clear from reading the code.
There is nothing in the code explains:
PathError
is converted toIError
.fn (err PathError) msg() string
method is actually present.With an explicit implementation, the example is clear:
It's now clear that the
PathError
declaration is intended to implementIError
- we don't need the comment intry_open
to explain what's going on, and there is no mystery as to why themsg
method exists, which previously looked like it might have been an unused method.2. Error Reporting
From the example above, also consider the fact that error reporting can now report the error where the error is - for example, if you were to omit the
msg
method, you would get an error saying themsg
method is required to correctly implement theIError
interface. In other words,implements IError
explains our intent to implement that interface, which the reader (and compiler) could otherwise only assume based on usage.Let's consider the interfaces example from the documentation:
Here, the addition of implements makes it clear that there is an intended relationship between
Cat
andDog
and theSpeaker
interface - you know this in advance when you see the first declaration. Contrast this with the example in the manual, where no relationship is established unless you read through the calling code, which I've intentionally omitted from the updated example here - there is no saying the calling code for an interface is located in the same file.In terms of error reporting, we can now point to
Dog
orCat
as having a problem, if the required methods and fields aren't present, or have the wrong type, etc. - rather than pointing at many call sites having potential problems, we can now point at a single declaration as definitely having a problem, e.g. "missing field" or "missing method".One, clear error versus many potential errors.
Proposed Solution
No response
Other Information
This feature could help with modularity and reuse as well.
For example, consider the example presented here, where you need to implement a
Context
type:There is no problem with this example per se, but consider the case where I'd like to create a module that provides a reusable middleware, which will establish a
session_id
- in order to work with your custom context, this middleware would need you to add thesession_id string
to yourContext
type, and this would need to be part of an interface.If you see the declaration above, you would have no idea why the
session_id
is there, and apub struct Context implements ISessionContext
declaration would help make that relationship clear and explicit - it explains why you would expect thisContext
implementation to work with the session middleware, and ensures we point to the source of that problem.If some day we have an ecosystem of reusable middleware, it would be helpful to understand from someone's
Context
declaration how they intended for it to work with the integrated middleware components.Acknowledgements
Version used
0.4.7
Environment details (OS name and version, etc.)
Note
You can use the 👍 reaction to increase the issue's priority for developers.
Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.
The text was updated successfully, but these errors were encountered: