[swift-evolution] Proposal: Split extensions into implementing methods and adding static functions Was: [swift-evolution-announce] [Review] SE-0164: Remove final support in protocol extensions

Xiaodi Wu xiaodi.wu at gmail.com
Sun Apr 16 18:01:31 CDT 2017


This continues to forbid use cases that are critical.

For instance, I am writing a library that vends additional conformances for
Float and Double. Any numerics library would need to do the same.

Your design would eliminate all such libraries, which is a non-starter. I
am not sure what defects you are trying to solve with this proposal.
On Sun, Apr 16, 2017 at 17:51 Howard Lovatt <howard.lovatt at gmail.com> wrote:

> @Brent,
>
> I have updated the proposal to address your concerns, in particular I
> don't see that retrospectively adding methods and protocols has been
> removed it has just had its ugly corners rounded. See revised proposal
> below particularly the end of section "Retrospectively adding protocols
> and methods" and new section "Justification".
>
> Hope this convinces you that the change is worthwhile.
>
> -- Howard.
>
> ====================================
>
> # Proposal: Split extension usage up 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 |
>
>
> ## 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!
>
>
> Extension methods can also cause compatibility problems between modules,
> consider:
>
>
> In Module A
>
>     extension Int: P {
>
>         func m() -> String { print("A.m") }
>
>     }
>
>
> In Module B
>
>     extension Int: P {
>
>         func m() -> String { print("B.m") }
>
>     }
>
>
> In Module C
>
>     import A
>
>     import B // Should this be an error
>
>     let i = 0
>
>     i.m() // Should it return A.m or B.m?
>
>
> This proposal cures the above two problems by separating extension methods
> into two seperate use cases: implementations for methods and adding methods
> and protocols retrospectively.
>
>
> ## Implementing methods
>
>
> If the extension is in the same file as the protocol/struct/enum/class
> declaration then it implements the methods and is dispatched using a
> Vtable. EG:
>
>
> File P.swift
>
>     protocol/struct/enum/class 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/struct/enum/class declaration
>
>     }
>
>
> Same or other 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.
>
>
> In a protocol at present there is a difference in behaviour between a
> protocol that declares a method that is then implemented in an extension
> and a protocol that just has the method implemented in an extension and no
> declaration. This situation only applies to protocols, for
> structs/enumerated/classes you cannot declare in type and implement in
> extensions. The proposal unifies the behaviour of
> protocol/struct/enum/class with extensions and prevents the error of a
> minor typo between the protocol and extension adding two methods instead of
> generating an error.
>
>
> The implementation needed to achieve this proposal is that a value
> instance typed as a protocol is copied onto the heap, a pointer to its
> Vtable added, and it is passed as a pointer. IE it becomes a class
> instance. No change needed for a class instance typed as a protocol.
>
>
> ## 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. EG:
>
>
>     protocol P2 {
>
>         func m2P()
>
>     }
>
>     final extension S: P2 { // Note extension marked final
>
>         func m2P() { print("SP2.m2P") } // Implicitly final, completely
> implements P2
>
>         func m2E() { print("SP2.m2E") } // Implicitly final, not an
> existing method
>
>     }
>
>
> Which are called as any other method would be called:
>
>
>     let s = S()
>
>     s.m2P() // Prints SP2.m2P
>
>     s.m2E() // Prints SP2.m2E
>
>
> A method added by a final extension is is implicitly final, as the name
> would suggest, and cannot be overridden.
>
>
> Notes:
>
>
>   1. If the final extension adds a method, e.g. m2E, 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.
>
>
>   2. If the final extension adds a protocol then it must implement all the
> methods in that protocol that are not currently implemented.
>
>
>   3. If the final extension is outside of the file in which the
> protocol/struct/enum/class declaration is in then the extension and the
> methods can only have fileprivate or internal access. This prevents
> retrospective extensions from numerous modules clashing, since they are not
> exported outside of the module.
>
>
> When a type is extended inside a module with a final extension the
> extension is not exported. For example:
>
>
>     final extension Int: P2 {
>
>         func m2P() { print("Int.m2P") }
>
>     }
>
>
> If an exported function uses Int, e.g.:
>
>
>     public func f(_ x: Int) -> Int {
>
>         x.m2P()
>
>         return x
>
>     }
>
>
> Then when used in an external module both the input Int and the output Int
> are not extended with P2. However as the Int goes into f it gains P2
> conformance and when it leaves it looses P2 conformance. Thus inside and
> outside the module the behaviour is easily understood and consistent and
> doesn't clash with other final extensions in other modules.
>
>
> Taking the above example further an Int with P2 conformance is required by
> the user of a library; then it can simply and safely be provided, e.g.:
>
>
>     public class P2Int: P2 {
>
>         var value = 0
>
>         func m2P() { print("Int.m2P") }
>
>     }
>
>
> This type, P2Int, is easy to write, one line longer than a final
> extension, and can easily be used as both a P2 and an Int and does not
> clash with another Int extension from another module.
>
>
> ## 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 final extensions are not
> 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 and the
> feature only makes it into the language if it offers many advantages, has
> few disadvantages, and is not heavily overlapping with other features. This
> keeping it small and simple test is what extensions have failed in other
> languages.
>
>
> Experience from .NET can however be used to improve extensions. There is
> some excellent advice
> https://blogs.msdn.microsoft.com/vbteam/2007/03/10/extension-methods-best-practices-extension-methods-part-6/
> written by the VB .NET team when they added extensions to VB .NET. The
> best-practice advice can be summarised by the following quotes from the
> reference:
>
>
>   0. "In most real world applications these suggestions [the rest of the
> suggestions] can (and quite frankly should!) be completely ignored." This
> is an important observations, in your own code that is not intended for
> reuse; go for it, use extensions. The proposal importantly still allows
> this style of programming and in fact improves it by adding consistent
> behaviour and syntax between protocols/structs/enumerated/classes.
>
>
>  1. "Read the .NET Framework Class Library Design Guidelines." The
> equivalent for Swift is lacking at this stage. Probably because third party
> libraries are rare.
>
>
>   2. "Be wary of extension methods." This recommendation is formalised in
> the proposal by limiting final extensions to be fileprivate or internal.
>
>
>   3. "Put extension methods into their own namespace." This recommendation
> is formalised in the proposal by limiting final extensions to be
> fileprivate or internal.
>
>
>   4. "Think twice before extending types you don’t own."
>
>
>   5. "Prefer interface extensions over class extensions." Translation to
> Swift terminology provide default implementations for protocol methods. The
> proposal encourages this by eliminating a major gotcha with the current
> implementation, namely the proposal always dispatches via a Vtable to give
> consistent behaviour.
>
>
>   6. "Be as specific with the types you extend as possible." Translation
> to Swift terminology provide default implementations for protocol methods
> that extend other  protocols if there is a more specific behaviour that is
> relevent. The proposal encourages this by eliminating a major gotcha with
> the current implementation, namely the proposal always dispatches via a
> Vtable to give consistent behaviour.
>
>
> The proposal formalises these best practices from .NET whilst increasing
> consistence and without loosing the ability to use extensions heavily in
> your own one-off code to allow for rapid development. Most of the best
> practices are for better libraries, particularly third party, which is an
> important area for future Swift growth onto the server side. This proposal
> actively encourages this transition to large formal server side code
> without loosing the free wheeling nature of app code.
>
>
> ## 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
> extension adds 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 post-hoc adding protocols and methods. Syntax is
> added that clarifies the two use cases, the former are termed extensions
> and must be in the same file as the type is declared, and the latter are
> termed final extensions and can be in any file, however if they are not in
> the type's file the they can only have fileprivate or internal access.
>
>
> 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
> https://github.com/apple/swift-evolution/blob/master/proposals/0169-improve-interaction-between-private-declarations-and-extensions.md
> .
>
>
> ===================================================
>
>
> #Proposal: Split extension usage up into implementing methods and adding
> methods and protocols post-hoc
>
>
> Draft 2 (Added support for post-hoc conformance to a protocol - replaced
> static final extensions with final extensions)
>
>
> ## 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!
>
>
> Extension methods can also cause compatibility problems between modules,
> consider:
>
>
> In Module A
>
>     extension Int: P {
>
>         func m() -> String { print("A.m") }
>
>     }
>
>
> In Module B
>
>     extension Int: P {
>
>         func m() -> String { print("B.m") }
>
>     }
>
>
> In Module C
>
>     import A
>
>     import B // Should this be an error
>
>     let i = 0
>
>     i.m() // Should it return A.m or B.m?
>
>
> This proposal cures the above two problems by separating extension methods
> into two seperate use cases: implementations for methods and adding methods
> and protocols post-hoc.
>
>
> ## Implementing methods
>
>
> If the extension is in the same file as the protocol/struct/class
> declaration then it implements the methods and is dispatched using a
> Vtable. EG:
>
>
> File P.swift
>
>     protocol/struct/class 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/struct/class declaration
>
>     }
>
>
> Same or other 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.
>
>
> In a protocol at present there is a difference in behaviour between a
> protocol that declares a method that is then implemented in an extension
> and a protocol that just has the method implemented in an extension and no
> declaration. This situation only applies to protocols, for structs and
> classes you cannot declare in type and implement in extensions. The
> proposal unifies the behaviour of protocol/struct/class with extensions and
> prevents the error of a minor typo between the protocol and extension
> adding two methods instead of generating an error.
>
>
> The implementation needed to achieve this is that a value instance typed
> as a protocol is copied onto the heap, a pointer to its Vtable added, and
> it is passed as a pointer. IE it becomes a class instance. No change needed
> for a class instance typed as a protocol.
>
>
> ## Post-hoc 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/class
> declaration is in. EG:
>
>
>     protocol P2 {
>
>         func m2P()
>
>     }
>
>     final extension S: P2 { // Note extension marked final
>
>         func m2P() { print("SP2.m2P") } // Implicitly final, completely
> implements P2
>
>         func m2E() { print("SP2.m2E") } // Implicitly final, not an
> existing method
>
>     }
>
>
> Which are called as any other method would be called:
>
>
>     let s = S()
>
>     s.m2P() // Prints SP2.m2P
>
>     s.m2E() // Prints SP2.m2E
>
>
> A method added by a final extension is is implicitly final, as the name
> would suggest, and cannot be overridden.
>
>
> If the final extension:
>
>
>   1. Adds a method, e.g. m2E, 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 post-hoc adds
> the protocol.
>
>
>   2. Adds a protocol then it must implement all the methods in that
> protocol that are not currently implemented.
>
>
>   3. Is outside of the file in which the protocol/struct/class declaration
> is in then the extension and the methods can only have fileprivate or
> internal access. This prevents post-hoc extensions from numerous modules
> clashing, since they are not exported outside of the module.
>
>
> ## 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
> extension adds 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 post-hoc adding protocols and methods. Syntax is
> added that clarifies the two use cases, the former are termed extensions
> and must be in the same file as the type is declared, and the latter are
> termed final extensions and can be in any file, however if they are not in
> the type's file the they can only have fileprivate or internal access.
>
>
> 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
> https://github.com/apple/swift-evolution/blob/master/proposals/0169-improve-interaction-between-private-declarations-and-extensions.md
> .
>
>
> ====================================
>
> On 14 Apr 2017, at 8:17 am, Brent Royal-Gordon <brent at architechies.com>
> wrote:
>
> On Apr 13, 2017, at 3:10 PM, Howard Lovatt via swift-evolution <
> swift-evolution at swift.org> wrote:
>
>
> I don't see that retroactive conformance needs to be exportable. If it is
> exported then you cannot prevent clashes from two modules, this is a known
> problem in C#. Because of this and other problems with C# extensions, this
> style of extension were rejected by other language communities (notably
> Java and Scala).
>
>
> A better alternative for export is a new class that encapsulates the
> standard type but with added methods for the protocol to be added. This way
> there is no clash between modules. EG:
>
>
>    public protocol P {
>
>        func m() -> String
>
>    }
>
>    public class PInt: P {
>
>        var value = 0
>
>        func m() -> String { return "PI.m" }
>
>    }
>
>
> Howard, this would be very source-breaking and would fail to achieve
> fundamental goals of Swift's protocol design. Removing retroactive
> conformance is no more realistic than removing Objective-C bridging—another
> feature which introduces various ugly edge cases and tricky behaviors but
> is also non-negotiable.
>
> --
> Brent Royal-Gordon
> Architechies
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170416/2295d802/attachment.html>


More information about the swift-evolution mailing list