@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 <[email protected]> wrote:
>
> As far as I'm aware, eliminating retroactive conformances is a non-starter.
>
>
>> On Mon, Apr 10, 2017 at 21:44 Howard Lovatt <[email protected]> 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 <[email protected]> wrote:
>>>
>>> On Mon, Apr 10, 2017 at 5:35 PM, Howard Lovatt via swift-evolution
>>> <[email protected]> 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 <[email protected]> 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
>>>>>> <[email protected]> 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
>>> [email protected]
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>>
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution