[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
Thu Apr 13 07:31:17 CDT 2017


The retroactive conformance needs to be exportable. If one cannot vend a
library that conforms standard library types to new protocols, then that is
a non-starter.


On Thu, Apr 13, 2017 at 06:07 Howard Lovatt <howard.lovatt at gmail.com> wrote:

> @Xiaodi,
>
> You can safely post-hoc add protocols and methods provided that they are
> final, do not override, and are not exported from the module. See version 2
> of the proposal below.
>
> -- Howard.
>
> # 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 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 11 Apr 2017, at 1:26 pm, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>
> As far as I'm aware, eliminating retroactive conformances is a non-starter.
>
>
> On Mon, Apr 10, 2017 at 21:44 Howard Lovatt <howard.lovatt at gmail.com>
> wrote:
>
>> @Xiaodi,
>>
>> You make two drugs.
>>
>> 1. Deliberately making retroactive conformance outside of the file in
>> which the type is declared illegal because of the problems it causes. See
>> all the questions on Swift Users and watch people learning Swift get caught
>> out.
>>
>> 2. Outside of the file in which the type is declared the static final
>> extension is restricted to internal or fileprivate so that multiple modules
>> can add static final extensions without clashes.
>>
>>
>> -- Howard.
>>
>> On 11 Apr 2017, at 8:51 am, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>
>> On Mon, Apr 10, 2017 at 5:35 PM, Howard Lovatt via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>> In response to Jordan Rose's comment I suggest the following change:
>>
>> Proposal: Split extension usage up into implementing methods and adding
>> static functions
>>
>>
>> Currently extension methods are confusing because they have different
>> dispatch rules for the same syntax. EG:
>>
>>
>>     protocol P {
>>
>>         func m()
>>
>>     }
>>
>>     extension P {
>>
>>         func m() { print("P.m") }
>>
>>     }
>>
>>     struct S: P {
>>
>>         func m() { print("S.m") }
>>
>>     }
>>
>>     val p: P = S() // Note typed as P
>>
>>     p.m() // Surprisingly prints P.m even though S implements its own m
>>
>>
>> This is incorrect. This prints "S.m", not "P.m".
>>
>>
>>     val s = S() // Note typed as S
>>
>>     s.m() // Prints S.m as expected
>>
>>
>> This proposal cures the above problem by separating extension methods
>> into two seperate use cases: implementations for methods and adding static
>> functions.
>>
>>
>> First 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()
>>
>>     }
>>
>>     extension P {
>>
>>         func m() { print("P.m") }
>>
>>     }
>>
>>
>> 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
>>
>>
>> Requiring `override` breaks retroactive conformance of types to
>> protocols. This idea has been brought up over half a dozen times. Each time
>> it fails in not being able to accommodate retroactive conformance.
>>
>>
>>     }
>>
>>     val 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.
>>
>>
>> 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.
>>
>>
>> The second use case is adding static functions.
>>
>>
>> A new type of extension is proposed, a static final extension, which can
>> be either in or outside the file in which the protocol/struct/class
>> declaration is in. EG:
>>
>>
>>     static final extension P { // Note extension marked static final
>>
>>         func m() { print("P.m") }
>>
>>     }
>>
>>
>> Which is called as any other static function would be called:
>>
>>
>>     val s = S()
>>
>>     P.m(s) // Prints P.m as expected
>>
>>
>> The new static final extension is shorthand, particularly in the case of
>> multiple functions, for:
>>
>>
>>     extension P {
>>
>>         static final func m(_ this: P) { print("P.m") }
>>
>>     }
>>
>>
>> If the static final extension is outside of the file in which the
>> protocol/struct/class declaration is in then the extension and the methods
>> can only have fileprivate and internal access.
>>
>>
>> What is the use case for having this restriction? What is the problem you
>> are trying to solve?
>>
>>
>>
>> As at present protocol/struct/class can have both a static and instance
>> method of the same name, m in the case of the example, because the usage
>> syntax is distinct. As at present, static final extensions, both the
>> extension and the individual functions, can have where clauses.
>>
>>
>> In summary.
>>
>>
>> The proposal formalises the split use of extensions into their two uses:
>> implementing methods and adding static functions. Syntax is added that
>> clarifies both for declarations and usage which type of extension is
>> provided/in use.
>>
>>
>> Note the distinction between an extension in the same file and in a
>> separate file is consistent with the proposed use of private in
>> https://github.com/apple/swift-evolution/blob/master/proposals/0169-improve-interaction-between-private-declarations-and-extensions.md
>> .
>>
>> Comments?
>>
>> -- Howard.
>>
>> On 7 Apr 2017, at 4:49 am, Jordan Rose <jordan_rose at apple.com> wrote:
>>
>> [Proposal:
>> https://github.com/apple/swift-evolution/blob/master/proposals/0164-remove-final-support-in-protocol-extensions.md
>> ]
>>
>> On Apr 5, 2017, at 16:15, Howard Lovatt via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>> The review of SE-0164 "Remove final support in protocol extensions"
>>
>>
>>    - What is your evaluation of the proposal?
>>
>> The present situation isn't great. People get confused about which method
>> will called with protocol extensions. Seems like every week there is a
>> variation on this confusion on Swift Users mailing list. Therefore
>> something needs to be done.
>>
>> However I am not keen on this proposal since it makes behaviour
>> inconsistent between methods in protocol extensions, classes, and structs.
>>
>> I think a better solution would be one of the following alternatives:
>>
>>   1. Must use final and final means it cannot be overridden; or
>>   2. If not final dispatches using a table like a class and if marked
>> final cannot be overridden and if marked dynamic uses obj-c dispatching; or
>>   3. Must be marked dynamic and uses obj-c dispatching.
>>
>> My preference would be option 2 but I think any of the three is superior
>> to the present situation or the proposal.
>>
>>
>> People have suggested all of these before, but none of them are obviously
>> correct. It's true that we have a difference between extension members that
>> satisfy requirements and those that don't, and that that confuses people.
>> However, an extension-only member of one protocol can be used to satisfy
>> the requirements of another protocol today, which is a tool for code reuse.
>>
>> (I *think* we managed to convince everyone that it's just a bug that a
>> protocol extension method that satisfies a requirement cannot be overridden
>> in a subclass, so at least that isn't an issue on top of the rest of this.)
>>
>> Oh, and we can't retroactively add members of a protocol extension to
>> existing adopters, which is why protocol extension members cannot be @objc.
>> There are limited circumstances where that would be safe, but that would be
>> a separate proposal.
>>
>> Jordan
>>
>>
>> _______________________________________________
>> 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/20170413/80640a41/attachment.html>


More information about the swift-evolution mailing list