Personally I would like the following rules: 1. If a method implements or overrides any method in a class, struct, or extension, whether declared in a protocol or class, then it must be tagged with override. 2. If a method is implemented in an extension and not declared previously in a protocol then in should be tagged final (this is another swift-evolution proposal already)
If both were adopted then it would be clear what was happening. At present the compiler also struggles, if you make a minor error when overriding a protocol method all the compiler can do is tell you that the struct/class does not conform. It doesn't tell you where the error is, which might be in an extension in a different file. If the methods were tagged with override the compiler would give a better error message and importantly direct you to the problem. On Wednesday, 6 January 2016, Xiaodi Wu via swift-evolution < [email protected] <javascript:_e(%7B%7D,'cvml','[email protected]');>> wrote: > Dipping my toes in the water, so to speak, with a suggestion that came to > mind. Not an expert by any means, so hopefully this isn't too laughably off > the mark. As background, I work in the biomedical sciences; with few > exceptions, those of us who write code are non-experts who learn as we go > along. What's been attractive about Swift is that it's been designed with > approachability in mind for beginning coders and those with a rudimentary > understanding of languages in the C family. Difficulty in syntax and > sophistication of the computer science concepts exposed are hurdles that > can be overcome, but what keeps me up at night are potentially subtle > things maybe easy for the expert that I or my colleagues just might not > know about, which then lead to code that appears correct at first glance, > compiles, but executes in subtly unintended ways. Those things in my field > are what could lead to retracted papers, ruined careers, etc.... > > This is something along those lines; also somewhat of a reprise of a theme > that's been raised tangentially in a few threads within the past month > (e.g.: > https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/001125.html), > but I think the particular solution I'm envisioning hasn't been put forward. > > The issue at hand: > > Consider the following (contrived) example (call it version 1)-- > > class Animal { > func makeNoise() -> String { > return "generic sound" > } > } > > class Cow: Animal { > override func makeNoise() -> String { > return "moo" > } > } > > class Sheep: Animal { > override func makeNoise() -> String { > return "bah" > } > } > > let cow = Cow() > cow.makeNoise() // "moo" > (cow as Animal).makeNoise() // "moo" > > let farm: [Animal] = [Cow(), Cow(), Sheep()] > let noises: [String] = farm.map { $0.makeNoise() } > // ["moo", "moo", "bah"] > > Now refactor to use a protocol. I'll give two versions, differing by a > single line. > > Version 2A-- > > protocol Animal { } > > extension Animal { > func makeNoise() -> String { > return "generic sound" > } > } > > class Cow: Animal { > func makeNoise() -> String { > return "moo" > } > } > > class Sheep: Animal { > func makeNoise() -> String { > return "bah" > } > } > > let cow = Cow() > cow.makeNoise() // "moo" > (cow as Animal).makeNoise() // "generic sound" > > let farm: [Animal] = [Cow(), Cow(), Sheep()] > let noises: [String] = farm.map { $0.makeNoise() } > // ["generic sound", "generic sound", "generic sound"] > > Version 2B-- > > protocol Animal { > func makeNoise() -> String > } > > extension Animal { > func makeNoise() -> String { > return "generic sound" > } > } > > class Cow: Animal { > func makeNoise() -> String { > return "moo" > } > } > > class Sheep: Animal { > func makeNoise() -> String { > return "bah" > } > } > > let cow = Cow() > cow.makeNoise() // "moo" > (cow as Animal).makeNoise() // "moo" > > let farm: [Animal] = [Cow(), Cow(), Sheep()] > let noises: [String] = farm.map { $0.makeNoise() } > // ["moo", "moo", "bah"] > > To be sure, this difference in behavior can be justified in a variety of > ways, but it is nonetheless a surprising result at first glance. Most > concerning is that it is possible to imagine a scenario in which the > protocol in question is one provided in a third-party library, or even the > Swift standard library, while I'm writing a struct or class that implements > that protocol. > > Suppose that between versions of a third-party library the Animal protocol > changes from version 2A to version 2B. My struct or class that implements > the protocol would compile without changes, and cow.makeNoise() would even > give the same result, yet there would be differences in how my code works > that would be difficult to track down. An expert would be able to spot the > difference on examination of the protocol declaration, but one would have > to be knowledgeable already about this particular issue to know what to > look for. This seems like a gotcha that can be avoided. > > Proposed solution: > > I think others have tried to approach this from a different angle (see, > for example: > https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151207/001933.html). > My view is that there are two points potentially to address. > > (1) func makeNoise() -> String { ... } within the protocol extension > indicates two different things in versions 2A and 2B (since one is > dynamically dispatched and the other is not), yet the syntax is > indistinguishable. > > (2) Within a class or struct implementing a protocol, a method with the > same name as that of a method in a protocol extension (potentially in > another file, maybe not written by the same person) behaves differently > depending on whether that method name is declared in the protocol itself > (potentially in a third file, written by a third person), yet the syntax is > indistinguishable within the implementing class or struct. If the protocol > changes, whether a method is dynamically dispatched or statically > dispatched could change too, yet code in a class or struct implementing > that protocol that now behaves differently compiles all the same; > implementors are not clued into the change and may not even be aware that > changes such as this could happen. > > What I'm thinking is a light-touch fix that would address (2), which would > largely mitigate the consequences of (1). Taking inspiration from syntax > used for methods in classes that override methods in superclasses, require > methods that override dynamically dispatched default implementations in > protocol extensions to use the override keyword. Likewise, forbid the > override keyword if the method being implemented instead 'masks' (would > that be the right word?) a statically dispatched method in a protocol > extension which can nonetheless be invoked by upcasting to the protocol. > > In other words, I propose that in the contrived example above, version 2B > (which behaves just like version 1) requires syntax just like that of > version 1 (in class Cow and class Sheep, override func makeNoise() -> > String { ... }). Meanwhile, version 2A, which doesn't quite behave like > version 1, forbids the same syntax. That way, anyone confused about what he > or she is getting into when implementing the protocol is notified at > compile time, and code that compiles in one scenario will not compile if > the underlying protocol declaration changes. > > I suppose quite a good counterargument would be that protocols exist to be > implemented, and implementations of methods required for conformance to a > protocol shouldn't need another keyword. I would be inclined to agree, but > in this case I'd point out that what would trigger the requirement for use > of the override keyword is the presence of a default implementation being > overridden, not the mere fact that a method declared within a protocol is > being implemented. > > Would this be something that is desirable to other users of the language? > Easy to implement? > > -- -- Howard.
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
