[Proposal] Interfaces are implemented implicitly #1647
Replies: 20 comments
-
This is my strongest argument that golang was not designed for scalability. Implicit interface implementation implies that method semantics are uniquely defined by the signature of the method. By this logic, all method level documentation is unnecessary, as the language specification will already define the semantics of all methods according to their signatures. Obviously the previous case doesn't hold under any current language. Therefore it is easy to conclude that implicit interface implementations in a language will inevitably result in an object claiming to adhere to the contract of an interface when in fact it does not (and likely was never intended to). Aside from the previous argument on language and application semantics, implicit interfaces causes major problems for IDEs. Renaming a method (F2) can result in almost unbounded propagation of the new name to completely unrelated scenarios through coincidental matches in interfaces. The IDE ends up needing to intentionally introduce potential semantic errors to avoid propagating too far. |
Beta Was this translation helpful? Give feedback.
-
💡 This sounds a lot like #164 |
Beta Was this translation helpful? Give feedback.
-
Implicit interfaces sounds a lot like #110 |
Beta Was this translation helpful? Give feedback.
-
Tip: Next time, put each proposal in a separate issue so that the discussion around each one can be kept separate. |
Beta Was this translation helpful? Give feedback.
-
@sharwell, isn't this "duck typing" request exactly what shapes will do (#164)? The IDE will have to support shapes somehow, where as you seem to be saying it can't (easily) do so. @AIEternity, interfaces won't be changed to work this way. But other features may be added that will do this. Would you therefore mind closing this issue and redirecting your thoughts on the matter to #164 and #110, please? |
Beta Was this translation helpful? Give feedback.
-
@DavidArno as far as I'm aware, that's referred to as a form of 'structural' typing, because you have to match the shape of the whole type (i.e. abstract types are defined by their shape, but it's still a type). Duck typing is what templates do in C++: it just has to have all the members you are using (i.e. abstract types cease to be a meaningful concept). Contrast these with nominal typing, where types are always 'matched' (for lack of a better term) by name. (This is at least how I use the terms day in day out) ... It's also not clear that Shapes (as per #110) would be structurally matched. I'm not convinced that's part of the proposal. I seem to have created a lot of confusion over at #110 when I panicked because I thought it was part of the proposal. The only 'official' feedback I got over there (as far as I could tell) was from Gafter, who very calmly explained that it was something that would need deciding, and that not having it structural was certainly a viable solution to the "how will the compiler/IDE cope" problem sharwell mentions. He also explained (at least as I understood it) that the "structural" bit in the title referred to the implicit implementation of declared implementations (just like implicit implementation of interface members: you still have to declare you are implementing the interface, it just matches the members themselves by name). I've not been through #164 as closely, but I don't see anything structural there from a quick look, and at the bottom is suggest that structural/duck typing would be a 'different direction'... so I'm hopeful :) |
Beta Was this translation helpful? Give feedback.
-
Interesting: I always thought duck typing and structural typing were two names for exactly the same thing. |
Beta Was this translation helpful? Give feedback.
-
@DavidArno I've always been terrible at terminology, but I do think it's a valuable distinction (even if I have the words wrong) |
Beta Was this translation helpful? Give feedback.
-
@DavidArno I'd say duck typing is implicit dynamic structural typing. Shapes will not (I think) support implicit typing, but their typing will definitely be structural and static. |
Beta Was this translation helpful? Give feedback.
-
@orthoxerox Reset: duck typing is seemingly necessarily dynamic, because it's defined in terms of runtime (i.e. depends on execution path)... that means I need a different word for what C++ does. |
Beta Was this translation helpful? Give feedback.
-
@VisualMelon I would contest that C++ templates are duck-typed, but I'd rather abandon the term "duck typing" altogether for being insufficiently precise. Shapes are static, explicit and structural. Classes/interfaces are static, explicit and nominal. |
Beta Was this translation helpful? Give feedback.
-
@orthoxerox I'd rather abandon it too... but mostly because I don't think it deserves the term 'typing'! I would agree now that C++ templates are not duck typing... the distinction between what happens at runtime and what might happen at runtime is one I'd not considered properly (does anyone have a name for this?). I'd stand by a tighter definition of structural as valuable though (i.e. as stated somewhere above). Do you have a reference for "Shapes are [...] structural"? (by whatever definition; I don't care so long as the reference qualifies it) |
Beta Was this translation helpful? Give feedback.
-
@VisualMelon the word "structural" is literally in the title on #110 😁 |
Beta Was this translation helpful? Give feedback.
-
@orthoxerox the whole daft discussion I sparked off there is precisely because I'm not convinced that the documentation associated says that those type-classes/shapes would actually be structurally typed (as opposed to 'defining structure', which is almost meaningless), e.g. are they not typed like Rust traits (which I'd consider purely nominal from my limited awareness)? We're at risk of covering old ground here, so it's probably best to kill this particular thread... I think we've settled the points relevant to this issue. |
Beta Was this translation helpful? Give feedback.
-
This is a horrible idea and could create so many unwanted effects just because you were too lazy to type Naming methods can already be hard enough, but now I have to worry about not using the same name that is used in any number of interfaces? Also, consider this: public interface ICake { void Bake(); }
public interface IPizza { void Bake(); }
public class Cookie
{
public void Bake();
} Is it an Beyond that, if you just saw this code: public class Foo()
{
public void Bar() { ... }
} And you weren't the other or you are revisitng this code after a long break, wouldn't you like to be able to know that it is implementing twenty interfaces? Also, you loose compile time safety. If I was relying on the fact that C# would assume what interface I was using because of the method signature and I changed that to some overload to include another If I tried doing that with explicit method implimentation, it won't let me compile until I impliment the normal method. |
Beta Was this translation helpful? Give feedback.
-
What happens when the class is a third party one? Adding that interface constraint isn't then possible. |
Beta Was this translation helpful? Give feedback.
-
It's kind of moot. Changing existing interfaces to be implicitly implemented based on structure would break existing code. Interfaces are nominal as well as structural and it's intentional that a type explicitly take on the responsibility of implementing the contract of an interface. Shapes/concepts, as a new construct in the language, could offer this functionality. |
Beta Was this translation helpful? Give feedback.
-
I can just imagine what chaos would happen if I use reflection to get all types implementing some interface and getting back types that actually false positives because of they matched the interface accidentally... |
Beta Was this translation helpful? Give feedback.
-
Hi @AIEternity! A lot of people dislike this idea because they don't like structural typing, but I don't think that's what you actually wrote out. It sounds like what you want is a way to abstract numeric computation, like the I think this is a good idea. Multiple people on the language design team have also noted this as a problem, but the solution probably needs some work to integrate more with the existing ecosystem. #164 seems to have a lot of the same goals, but with a slightly different approach. I'd expect to see a lot more work on things like this in the future. |
Beta Was this translation helpful? Give feedback.
-
I fail to see how #110 (which I'm in favor of) is exactly relevant here without using generics. Can someone enlighten me? I'm in favor of the idea of "shapes" as I'll call them, as you (a) might not be able to modify the class to add an interface, or (b) doing so is not possible without circular references or polluting a higher-up library with lower-level concerns. Essentially what I'm envisioning (without using generic concepts) is a compiler-generated adapter, which would require no CLR changes. You all have discussed shapes of behavior, but here's an example of a data shape use case our team came up with when discussing this idea. For the example below, assume Customer is a domain model class in a separate library that perhaps cannot be modified, and does not implement the interface below. // in InterestRateCalculator.dll, with no reference to the domain model assy:
public interface ICustomerAccountInfo
{
decimal ContractedInterestRate { get; }
bool AccountIsDelinquent { get; }
decimal? PromotionalInterestRate { get; }
DateTime? PromotionalInterestRateExpiration { get; }
// ... let's say there's 8 other properties here like the ones above
}
// in InterestRateCalculator.dll:
public static class InterestRate
{
public static decimal GetCurrentInterestRate(ICustomerAccountInfo info) { ... }
}
// somewhere else, that does have a reference to both InterestRateCalculator.dll and the domain model
public void DoSomething()
{
Customer customer = GetCustomerFromSomeDataSource();
// this would be a compiler error if Customer does not have the properties of ICustomerAccountInfo
var interestRate = InterestRate.GetCurrentInterestRate(customer);
// ...
} Yes, you could pass those values as parameters instead. But that would be 12 parameters, which is generally frowned upon. Yes, the author of InterestRateCalculator could create an additional class/struct that implements And yes, the caller could write their own class to implement the interface, but that has the mutable-vs-parameters problem above. Or finally, you have the option of having to write an adapter when the compiler could do it for you. You might also be saying, doesn't this pose a problem if Customer's shape deviates from ICustomerAccountInfo? Yes it does, it would become a compiler error. In that case, you would be able to use an anonymous type (or named class/struct if you wanted to), perhaps with a fun new spread operator (offtopic): public void DoSomething()
{
Customer customer = GetCustomerFromSomeDataSource();
var interestRate = InterestRate.GetCurrentInterestRate(new {
ContractedInterestRate = customer.InterestRate,
...customer
});
} The benefit here is that the author of InterestRateCalculator is not telling you how to get the data/behavior to it, just what data/behavior it needs, without having to write an adapter yourself. I'd imagine the generated adapter from the first example would look something like: public class SomeGeneratedAnonymousClassName : ICustomerAccountInfo
{
private readonly Customer _customer;
public SomeGeneratedAnonymousClassName(Customer customer) { _customer = customer; }
public decimal ContractedInterestRate => _customer.ContractedInterestRate;
public bool AccountIsDelinquent => _customer.AccountIsDelinquent;
// ... and so on
} The generated class from the anonymous class/spread example would not need a separate adapter; the generated anonymous class could implement the interface directly. If the C# team wanted to make this auto-generation of an adapter explicit instead of implicit, perhaps a new public void DoSomething()
{
Customer customer = GetCustomerFromSomeDataSource();
var interestRate = InterestRate.GetCurrentInterestRate(shapeof(customer));
}
After thinking about it though, I prefer the implicit approach. It should not break any existing code, because existing code in this form would not compile if |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
I propose realize Interfaces are implemented implicitly like golang.
I want to be able to define operators overloading in Interface. In the end I want to be able to write next code:
Beta Was this translation helpful? Give feedback.
All reactions