[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