[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
Vladimir.S
svabox at gmail.com
Tue Apr 11 07:14:38 CDT 2017
On 11.04.2017 1:51, Xiaodi Wu via swift-evolution wrote:
> On Mon, Apr 10, 2017 at 5:35 PM, Howard Lovatt via swift-evolution
> <swift-evolution at swift.org <mailto: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".
I believe the discussion was started is about protocol extensions methods that was
not declared as protocol requirements:
protocol P {
func foo()
}
struct S : P {
func foo() {print("FOO from S")}
}
//----------------
extension P {
func foo() {print("FOO default implementation from protocol")}
// notice: bar was not declared as protocol requirement
func bar() {print("BAR default implementation from protocol")}
}
extension S {
func bar() {print("BAR from S")}
}
var p : P = S()
p.foo() // FOO from S
p.bar() // BAR default implementation from protocol
var s :S = S()
s.foo() // FOO from S
s.bar() // BAR from S
This is actually a big confusion point raised periodically in list and on
stackoverflow etc. When you know how it works currently - you have no questions, but
before this you can think that P.bar is same "thing" as P.foo. IMO Swift should help
to clarify this situation with some kind of keyword/warning or actually make P.bar
use the same rules as P.foo. After all, P.bar is a method in P protocol, S conforms
to P protocol, so p.bar() logically should call S.bar() implementation.
At least IMO we need some marker for such protocol extension method that is not
declared in protocol itself as requirement.
extension P {
func foo() {...}
func bar() {...} // Warning: 'bar' is not protocol requirement, use 'notrequirement'
keyword
}
var p : P = S()
p.foo()
p.bar() // Warning: 'bar' is not-a-requirement method defined without
'notrequirement' keyword
So, to fix the warning:
extension P {
func foo() {...}
notrequirement func bar() {...}
}
('notrequirement' is not a proposal, just an example)
Personally I'd even require a "marker" on caller side, for example
p.bar() // Warning: 'bar' is not-a-requirement method, exact implementation of
P.bar() will be called. Use explicit casting to silence the warning
(p as P).bar() // ok
>
>
> 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.
FWIW Actually, there were a number of suggestions where this point is solved with
some additional keywords/syntax. But as I remember they also was not accepted.
IIRC something like this:
protocol P {
func foo()
}
struct S : P {
func foo() {} // no default implementation was known at the moment of
// *writing* this code
}
//another file
extension P {
func foo() {} // this causes warning for S declaration "'override' is missed"
func bar() {} // not-a-requrement
}
extension S {
overriden foo() // silence the warning, notice 'overriden', no body
override bar() {} // so S.bar should be called on S instance typed as P
}
Or this case:
struct S {
func foo() {}
func bar() {}
}
// another file
protocol P {
func foo()
}
extension P {
func foo() {}
func bar() {} // not-a-requrement
}
// another file
extension S : P {
overriden foo() // silence the warning
overriden bar() // silence the warning
// or even:
//overriden P // instead of separate method names of P protocol
}
>
>
> }
>
> 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
>
<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
> <mailto:jordan_rose at apple.com>> wrote:
>
>> [Proposal:
https://github.com/apple/swift-evolution/blob/master/proposals/0164-remove-final-support-in-protocol-extensions.md
>>
<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 <mailto: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 <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution
> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>
>
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
More information about the swift-evolution
mailing list