[swift-evolution] Overriding specific methods when adopting protocols with extension

Björn Forster bjoern.forster at googlemail.com
Thu May 11 05:24:38 CDT 2017


As the point Allowing subclasses to override requirements satisfied by
defaults (*)in the generics manifesto is potentially source breaking in the
way the software behaves, is it still on the table for Swift 4?
Maybe someone from the core team can comment on this? :-)


On Wed, May 10, 2017 at 11:00 AM, Howard Lovatt via swift-evolution <
swift-evolution at swift.org> wrote:

> Here is a proposal I put forwards last month on the same topic.
>
> ============================================================
> =====================
>
> # Proposal: Split extension into implementing methods and adding methods
> and protocols retrospectively
>
> ## Revision history
>
> | Version | Date               | Comment       |
> |---------|--------------|--------------|
> | Draft 1   | 11 April 2017 | Initial version |
> | Draft 2  | 13 April 2017 | Added support for post-hoc conformance to a
> protocol - replaced static final extensions with final extensions |
> | Draft 3 | 17 April 2017 | Added justification section |
> | Draft 4 | 2 May 2017   | Allow final extensions to be public and allow
> ad-hoc code reuse |
>
> ## Introduction
>
> Currently extension methods are confusing because they have different
> dispatch rules for the same calling syntax. EG:
>
>     public protocol P {
>         func mP() -> String
>      }
>     extension P {
>         func mP() -> String { return "P.mP" }
>         func mE() -> String { return "P.mE" }
>     }
>     struct S: P {
>         func mP() -> String { return "S.mP" }
>         func mE() -> String { return "S.mE" }
>     }
>     let s = S()
>     s.mP() // S.mP as expected
>     s.mE() // S.mE as expected
>     let p: P = s // Note: s now typed as P
>     p.mP() // S.mP as expected
>     p.mE() // P.mE unexpected!
>
> The situation with classes is even more confusing:
>
>     class C: P { /*gets the protocol extensions*/ }
>     let pC: P = C()
>     pC.mP() // P.mP as expected!
>     pC.mE() // P.mE as expected!
>     class D: C {
>         /*override not allowed!*/ func mP() -> String { return "D.mP" }
>         /*override not allowed!*/ func mE() -> String { return "D.mE" }
>     }
>     let pD: P = D()
>     pD.mP() // P.mP unexpected!
>     pD.mE() // P.mE unexpected!
>
> This proposal cures the above two problem by separating extension methods
> into two seperate use cases: implementations for methods and adding
> methods and protocols retrospectively. The proposal still retains
> retroactively adding protocol conformance and ad-hoc code reuse, however
> these are made easy to understand and safe.
>
> ## Implementing methods in same file as type declaration
>
> If the extension is in the **same** file as the type declaration then its
> implemented methods are dispatched using a Vtable for protocols and classes
> and statically for structs and enums. EG:
>
> File P.swift
>
>     protocol P {
>         // func m() not declared in type since it is added by the
> extension, under this proposal it is an error to include a declaration in
> a type **and** in an extension
>     }
>     extension P {
>         func m() { print("P.m") } // m is added to the protocol declaration
>     }
>
> Same or another file
>
>     struct S: P {
>         override func m() { print("S.m") } // Note override required
> because m already has an implementation from the extension
>     }
>     let p: P = S() // Note typed as P
>     p.m() // Now prints S.m as expected
>
> Extensions in the same file as the declaration can have any access, can
> be final, and can have where clauses and provide inheritable
> implementations. Ad-hoc code reuse is supported, in particular if a
> class/enum/strict already had a method, m say, and a protocol, P say,
> required an m then an extension that added P would not need to provide m
> (i.e. as at present).
>
> In a protocol at present you can declare a method that is then implemented
> in an extension without the use of the override keyword. This situation
> only applies to protocols, for structs/enumerated/classes you cannot
> declare in type and implement in an extension at all. This proposal
> unifies the behaviour of protocol/struct/enum/class with extensions and
> also prevents the error of a minor typo between the protocol and extension adding
> two methods instead of generating an error, by requiring either:
>
>   1. The method is only declared in the protocol and not in any extensions and
> is therefore abstract
>   2. The method is only in one extension and not in the protocol
>
> A method can be abstract in one protocol and implemented in a second
> protocol that extends the first.
>
> The implementation needed to achieve this proposal for a protocol is that
> a value instance typed as a protocol is copied onto the heap, a pointer to
> its Vtable added, and its address passed/copied (i.e. it becomes a class
> instance). No change is needed for a class instance typed as a protocol,
> which unlike at present can now be passed/copied as a protocol directly.
> Think of a protocol as like an abstract class; cannot be instantiated like
> an abstract class and which possibly has abstract methods, but in different
> in that it cannot have fields but can be multiply inherited.
>
> Static and final methods implemented in extensions are not part of the
> Vtable and are statically dispatched, i.e. no change from current Swift for
> static but final now has the expected meaning for a protocol. Dispatching
> for structs and classes unchanged.
>
> ## Retrospectively adding protocols and methods
>
> A new type of extension is proposed, a `final extension`, which can be
> either in or outside the file in which the protocol/struct/enum/class
> declaration is in:
>
> File P.swift
>
>     protocol P {}
>     extension P {
>         func m() { print("P.m") } // m is added to the protocol declaration
>     }
>
> Same or another file
>
>     struct S: P {} // Inherits m from the extension
>
> In file P2.swift
>
>     protocol P2 {
>         func m2()
>         func m() // Note same signature as P.m which S already implements
>     }
>
> In same or another file
>
>     final extension S: P2 { // Note extension marked final
>         // m cannot be provided because S already has a final m (the
> inherited method must be final)
>         func m2() { print("SP2.m2") } // Implicitly final, completes
> implementation of P2
>         func mE() { print("SP2.mE") } // Implicitly final, not an existing
> method
>     }
>
> Which are called as any other method would be called:
>
>     let s = S() // or S() as P2 or s: P2
>     s.m() // Prints S.m
>     s.m2() // Prints SP2.m2
>     s.mE() // Prints SP2.mE
>
> Notes:
>
>   1. A method added by a `final extension`, e.g. `mE`, is implicitly
> final (as the name would suggest).
>
>   2. If the `final extension` adds a method, e.g. `mE`, that method
> cannot already exist. IE a `final extension` cannot override an existing
> method or implement a protocol declared method that lacks an implementation
> (unless it also adds the protocol). This is retroactively adding a method.
> Also see next point.
>
>   3. If the `final extension` adds a protocol, e.g. `P2`, then it must
> implement all the methods in that protocol that are not  implemented, e.g.
> `m2`. This is retroactively adding protocol conformance. Also see next
> point.
>
>   4. If the `final extension` adds a protocol, e.g. `P2`, then it
> inherits all the methods in that protocol that are implemented, e.g. `m`.
> These inherited methods must be final. This is ad-hoc code reuse of final
> methods when retroactively adding protocol conformance.
>
> Final-extensions can have `where` clauses.
>
> The implementation for a `final extension` is always static dispatching.
> That is why all methods involved in a `final extension` are final. The
> compiler always knows that the method can be called statically and there is
> no need for a Vtable entry for any of the methods, it is as though the
> methods were declared static but with the more convenient syntax of a
> normal method.
>
> ## Justification
>
> The aim of Swift is nothing more than dominating the world. Using the
> current, April 2017, https://www.tiobe.com/tiobe-index/
>  index of job adverts for programmers the languages that are in demand
> are: Java 15.568%, C 6.966%, C++ 4.554%, C# 3.579%, Python 3.457%, PHP
> 3.376%, Visual Basic .NET 3.251%, JavaScript 2.851%, Delphi/Object Pascal
> 2.816%, Perl 2.413%, Ruby 2.310%, and Swift 2.287%. So Swift at 12th is
> doing very well for a new language and is already above Objective-C at
> 14th. However there is obviously a long way to go and the purpose of this
> proposal is to help with this climb.
>
> A characteristic of many of the languages above Swift in the Tiobe Index
> is that they have major third party libraries; for some languages they are
> almost defined by their third part libraries, e.g. Ruby for Rails. A major
> part of this proposal is to make extensions safe when using multiple
> libraries from different venders. In particular, the two forms of
> extensions in this proposal can safely be exported.
>
> As part of Swift's goal of world domination is that it is meant to be easy
> to learn by a process of "successive disclosure". The current inconsistent
> behaviour of protocols and extensions hinders this process and is a
> common gotcha for newbies. This proposal eliminates that problem also.
>
> Extensions are not new in languages, they are part of the .NET languages
> for example. Since .NET popularised extensions they have been discussed
> by other language communities, particularly Java and Scala, and in the
> academic community (normally termed the Expression Problem) however they
> have not proved popular because of the problems they cause. Nearly all
> languages have a strong bias towards keeping the language small and simple
> and trade of the advantages of a feature against the disadvantages. The
> feature only makes it into the language if it offers many advantages, has
> few disadvantages, and is not heavily overlapping with other features. It
> is this keeping it small and simple test that extensions have failed in
> other languages, in particular their behaviour is hard to predict in a
> large code base with multiple third party libraries.
>
> However, extensions are popular in Swift and this proposals makes a few
> changes to them to make their behaviour predictable both in terms of third
> party libraries and in terms of method dispatch when the variable is typed
> as a protocol. Thereby still providing extensions including retroactive
> conformance and ad-hoc code reuse, but without the problems.
>
> ## Possible future work (not part of this proposal)
>
> This proposal will naturally allow bodies to be added to protocols
> directly rather than via an extension, since under the proposal the
> extensionadds the declaration to the type so it is a small step to allow
> the protocol methods to have an implementation.
>
> In an opposite sense to the above adding bodies to protocols, extensions could
> be allowed to add method declarations without bodies to protocols.
>
> The two above future work proposals, if both added, would add symmetry to
> where declarations and bodies may appear for protocols.
>
> ## In summary
>
> The proposal formalises the split use of extensions into their two uses:
> implementing methods and retroactively adding protocols and methods (in
> both cases including ad-hoc code reuse). The purpose of this split is to
> eliminate the problems associated with exceptions that have been well
> documented both with respect to Swift and other languages. Syntax is added
> that clarifies their two use cases (implementing methods and retroactively
> adding):
>
>   1. The former are termed extensions and must be in the same file as the
> type is declared, but can have non-final or final methods.
>   2. The latter are termed final-extensions and can be in any file,
> however final-extensions only have final methods.
>
> Note the distinction between an extension in the same file and in a
> separate file is consistent with the philosophy that there is special
> status to the same file as proposed for private in <
> http://github.com/apple/swift-evolution/blob/master/proposa
> ls/0169-improve-interaction-between-private-declarations-and-extensions.md
> >.
>
> ============================================================
> =====================
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170511/17a1a19e/attachment.html>


More information about the swift-evolution mailing list