[swift-evolution] Require use of override keyword to override dynamically dispatched methods defined in a protocol with a default implementation
Howard Lovatt
howard.lovatt at gmail.com
Tue Jan 5 20:10:30 CST 2016
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 <
swift-evolution at swift.org
<javascript:_e(%7B%7D,'cvml','swift-evolution at swift.org');>> 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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160106/7b350fe2/attachment.html>
More information about the swift-evolution
mailing list