[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
Howard Lovatt
howard.lovatt at gmail.com
Wed May 3 03:15:11 CDT 2017
@Goffredo,
"but while the core team has always stated "how else do we share code
between structs/value types?" The proposal provides a way to do this safely
-- Howard.
On 3 May 2017 at 17:33, Goffredo Marocchi <panajev at gmail.com> wrote:
>
>
> Sent from my iPhone
>
> On 3 May 2017, at 08:25, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>
> I definitely agree that it's a feature that _can_ be used unwisely, but
> the fact remains that it _is_ used pervasively in the standard library, and
> deliberately, and that such uses have gone through Swift Evolution and been
> approved.
>
>
> I think there is still sense in discussing it if we care about the
> language. I do agree that it is more likely and pragmatic (than removing it
> outright) to have a warning about it that people can turn on and treat as
> an error if they so choose, but while the core team has always stated "how
> else do we share code between structs/value types?" essentially, I am not
> sure why this should be allowed by default on class/reference types.
> To me it just does not make sense and it is a dangerous compromise we have
> and a regression from Objective-C.
>
> So there is really no point in discussing the removal of the feature, as
> it simply cannot happen.
> On Wed, May 3, 2017 at 02:01 Goffredo Marocchi <panajev at gmail.com> wrote:
>
>>
>>
>> Sent from my iPhone
>>
>> On 3 May 2017, at 01:44, Xiaodi Wu via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>> I don't understand your response. The standard library distinguishes
>> between protocol requirements with default implementations, which can be
>> overridden, and protocol extension methods, which can only be shadowed but
>> not overridden. It is used pervasively, from integers to collections. For
>> example, you can shadow but not override Sequence.contains. This is a
>> deliberate design choice. You are describing this as "undesirable";
>> however, it is not only highly desired (as expressed by many on this list
>> every time a proposal to change it has come up), it would be impossible to
>> maintain the current design of the standard library without this
>> distinction.
>>
>>
>> Being able to forbid overriding is fine, allowing shadowing without a
>> compiler warning/error is undesirable though. We should not have to dig up
>> 80's discussions about polymorphism to see that changing the actual code
>> being executed by casting a reference one way or the other is wrong. It may
>> be instrumentally good for some odd reason to someone, but I cannot see it
>> as intrinsically good.
>>
>> Shadowing a method in a class declared in a protocol extension, where it
>> also receives its default implementation, means that casting the instance
>> of the class to the class type or the protocol type would have different
>> methods being executed which can easily lead to mistakes beyond just
>> appearing confusing and wrong.
>>
>> (let alone that IMHO protocols/interfaces are meant to decouple code and
>> API contract and by definition shouldn't really have code in them, but it
>> is convenient for us as we need to share code between value types...).
>>
>> In your new design, how would you express a protocol which has some
>> requirements that have default implementations which can be overridden and
>> some implemented methods that cannot be overridden? If the answer is that
>> you cannot do that, then there is a problem.
>> On Tue, May 2, 2017 at 18:55 Howard Lovatt <howard.lovatt at gmail.com>
>> wrote:
>>
>>> You raise three points:
>>>
>>> 1. The harm is the inconsistent behaviour of extensions for both
>>> structs and classes, as outlined in the first section of the proposal. This
>>> is confusing for beginners and difficult to debug in large code bases,
>>> particularly those involving third party libraries were source is not
>>> available. I think a lot of the current experience with Swift does not use
>>> large code bases or third party libraries because the experience is with
>>> App development. As Swift moves to the server side large code bases and
>>> third party libraries will become the norm and the problems that result
>>> from the inconsistent behaviour will be magnified.
>>>
>>> 2. I am not saying that the current inconsistent behaviour is a bug I
>>> am saying it is undesirable. The Generics Manifesto also lists the
>>> behaviour as undesirable. Changes can and should be made for undesirable
>>> behaviour, e.g. the exclusivity proposal currently under review and the
>>> scoping of private which has been approved already.
>>>
>>> 3. I don't think making the changes required would be that large and
>>> the payoff of enabling large code bases and third party libraries is huge.
>>> The changes would involve:
>>>
>>> i. Moving some extensions from a seperate file into the file were
>>> the type was declared. This is good practice anyway and consistent with the
>>> behaviour of private, so no big deal. You should be doing this anyway. This
>>> would hardly affect the standard library.
>>>
>>> ii. Removing declarations from protocols that have implementations
>>> in extensions to *that* protocol. Easily caught by the compiler and Xcode
>>> could do the refactoring.
>>>
>>> ii. Marking retrospective conformance via extensions with final.
>>> Because this also means that the methods need to be final there could be
>>> some change. However, I would argue change for the good since it clarifies
>>> what happens, much like the changes needed for exclusivity are good. Note
>>> that since enum and struct methods are already final there is no change
>>> here other than adding final. Again a small change that the compiler/Xcode
>>> can do. The only area likely to have any issues are retrospective
>>> conformance for protocols and classes. This is a small price to pay for
>>> something as valuable as using third party libraries.
>>>
>>>
>>> -- Howard.
>>>
>>> On 2 May 2017, at 6:33 pm, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>
>>> I'm sorry, it's been a while. What is the harm that you are trying to
>>> cure, and how does this design accomplish that task?
>>>
>>> What you call "unexpected" is intentional. Many protocols, including the
>>> revised integer protocols that were just approved, distinguish between
>>> protocol requirements for which default implementations are provided and
>>> protocol extension methods which can be shadowed but not overridden. Both
>>> are used, and deliberately. Are you proposing to remove this feature? That
>>> simply cannot be done, as it would require re-designing vast amounts of the
>>> standard library that were just re-designed.
>>>
>>>
>>> On Tue, May 2, 2017 at 02:54 Howard Lovatt <howard.lovatt at gmail.com>
>>> wrote:
>>>
>>>> I haven't replied to these messages for a while since it has taken some
>>>> time to formulate a proposal that incorporates the feedback give - thanks
>>>> for the feedback.
>>>>
>>>> The new proposal allows retroactively added protocols to be exported
>>>> and ad-hoc code reuse, the two areas of concern.
>>>>
>>>> Comments?
>>>>
>>>> ============================================================
>>>> =====================
>>>>
>>>> # Proposal: Split extension 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 |
>>>> | Draft 4 | 2 May 2017 | Allow final extensions to be public and
>>>> allow ad-hoc code reuse |
>>>>
>>>> ## 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!
>>>>
>>>> The situation with classes is even more confusing:
>>>>
>>>> class C: P { /*gets the protocol extensions*/ }
>>>> let pC: P = C()
>>>> pC.mP() // P.mP as expected!
>>>> pC.mE() // P.mE as expected!
>>>> class D: C {
>>>> /*override not allowed!*/ func mP() -> String { return "D.mP" }
>>>> /*override not allowed!*/ func mE() -> String { return "D.mE" }
>>>> }
>>>> let pD: P = D()
>>>> pD.mP() // P.mP unexpected!
>>>> pD.mE() // P.mE unexpected!
>>>>
>>>> This proposal cures the above two problem by separating extension
>>>> methods into two seperate use cases: implementations for methods and adding
>>>> methods and protocols retrospectively. The proposal still retains
>>>> retroactively adding protocol conformance and ad-hoc code reuse, however
>>>> these are made easy to understand and safe.
>>>>
>>>> ## Implementing methods in same file as type declaration
>>>>
>>>> If the extension is in the **same** file as the type declaration then
>>>> its implemented methods are dispatched using a Vtable for protocols and
>>>> classes and statically for structs and enums. EG:
>>>>
>>>> File P.swift
>>>>
>>>> protocol 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
>>>> declaration
>>>> }
>>>>
>>>> Same or another 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. Ad-hoc code reuse is supported, in particular if a
>>>> class/enum/strict already had a method, m say, and a protocol, P say,
>>>> required an m then an extension that added P would not need to provide m
>>>> (i.e. as at present).
>>>>
>>>> In a protocol at present you can declare a method that is then
>>>> implemented in an extension without the use of the override keyword. This
>>>> situation only applies to protocols, for structs/enumerated/classes you
>>>> cannot declare in type and implement in an extension at all. This proposal
>>>> unifies the behaviour of protocol/struct/enum/class with extensions and
>>>> also prevents the error of a minor typo between the protocol and extension
>>>> adding two methods instead of generating an error, by requiring either:
>>>>
>>>> 1. The method is only declared in the protocol and not in any
>>>> extensions and is therefore abstract
>>>> 2. The method is only in one extension and not in the protocol
>>>>
>>>> A method can be abstract in one protocol and implemented in a second
>>>> protocol that extends the first.
>>>>
>>>> The implementation needed to achieve this proposal for a protocol is
>>>> that a value instance typed as a protocol is copied onto the heap, a
>>>> pointer to its Vtable added, and its address passed/copied (i.e. it becomes
>>>> a class instance). No change is needed for a class instance typed as a
>>>> protocol, which unlike at present can now be passed/copied as a protocol
>>>> directly. Think of a protocol as like an abstract class; cannot be
>>>> instantiated like an abstract class and which possibly has abstract
>>>> methods, but in different in that it cannot have fields but can be multiply
>>>> inherited.
>>>>
>>>> Static and final methods implemented in extensions are not part of the
>>>> Vtable and are statically dispatched, i.e. no change from current Swift for
>>>> static but final now has the expected meaning for a protocol. Dispatching
>>>> for structs and classes unchanged.
>>>>
>>>> ## 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:
>>>>
>>>> File P.swift
>>>>
>>>> protocol P {}
>>>> extension P {
>>>> func m() { print("P.m") } // m is added to the protocol
>>>> declaration
>>>> }
>>>>
>>>> Same or another file
>>>>
>>>> struct S: P {} // Inherits m from the extension
>>>>
>>>> In file P2.swift
>>>>
>>>> protocol P2 {
>>>> func m2()
>>>> func m() // Note same signature as P.m which S already
>>>> implements
>>>> }
>>>>
>>>> In same or another file
>>>>
>>>> final extension S: P2 { // Note extension marked final
>>>> // m cannot be provided because S already has a final m (the
>>>> inherited method must be final)
>>>> func m2() { print("SP2.m2") } // Implicitly final, completes
>>>> implementation of P2
>>>> func mE() { print("SP2.mE") } // Implicitly final, not an
>>>> existing method
>>>> }
>>>>
>>>> Which are called as any other method would be called:
>>>>
>>>> let s = S() // or S() as P2 or s: P2
>>>> s.m() // Prints S.m
>>>> s.m2() // Prints SP2.m2
>>>> s.mE() // Prints SP2.mE
>>>>
>>>> Notes:
>>>>
>>>> 1. A method added by a `final extension`, e.g. `mE`, is implicitly
>>>> final (as the name would suggest).
>>>>
>>>> 2. If the `final extension` adds a method, e.g. `mE`, 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). This is retroactively adding a method.
>>>> Also see next point.
>>>>
>>>> 3. If the `final extension` adds a protocol, e.g. `P2`, then it must
>>>> implement all the methods in that protocol that are not implemented, e.g.
>>>> `m2`. This is retroactively adding protocol conformance. Also see next
>>>> point.
>>>>
>>>> 4. If the `final extension` adds a protocol, e.g. `P2`, then it
>>>> inherits all the methods in that protocol that are implemented, e.g. `m`.
>>>> These inherited methods must be final. This is ad-hoc code reuse of final
>>>> methods when retroactively adding protocol conformance.
>>>>
>>>> Final-extensions can have `where` clauses.
>>>>
>>>> The implementation for a `final extension` is always static
>>>> dispatching. That is why all methods involved in a `final extension` are
>>>> final. The compiler always knows that the method can be called statically
>>>> and there is no need for a Vtable entry for any of the methods, it is as
>>>> though the methods were declared static but with the more convenient syntax
>>>> of a normal method.
>>>>
>>>> ## 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, the two forms of
>>>> extensions in this proposal can safely be 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. The
>>>> feature only makes it into the language if it offers many advantages, has
>>>> few disadvantages, and is not heavily overlapping with other features. It
>>>> is this keeping it small and simple test that extensions have failed in
>>>> other languages, in particular their behaviour is hard to predict in a
>>>> large code base with multiple third party libraries.
>>>>
>>>> However, extensions are popular in Swift and this proposals makes a few
>>>> changes to them to make their behaviour predictable both in terms of third
>>>> party libraries and in terms of method dispatch when the variable is typed
>>>> as a protocol. Thereby still providing extensions including retroactive
>>>> conformance and ad-hoc code reuse, but without the problems.
>>>>
>>>> ## 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 retroactively adding protocols and methods
>>>> (in both cases including ad-hoc code reuse). The purpose of this split is
>>>> to eliminate the problems associated with exceptions that have been well
>>>> documented both with respect to Swift and other languages. Syntax is added
>>>> that clarifies their two use cases (implementing methods and retroactively
>>>> adding):
>>>>
>>>> 1. The former are termed extensions and must be in the same file as
>>>> the type is declared, but can have non-final or final methods.
>>>> 2. The latter are termed final-extensions and can be in any file,
>>>> however final-extensions only have final methods.
>>>>
>>>> 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 <
>>>> http://github.com/apple/swift-evolution/blob/master/
>>>> proposals/0169-improve-interaction-between-private-
>>>> declarations-and-extensions.md>.
>>>>
>>>> ============================================================
>>>> =====================
>>>>
>>>> On Sun, 23 Apr 2017 at 6:36 am, Thorsten Seitz <tseitz42 at icloud.com>
>>>> wrote:
>>>>
>>>>> +1
>>>>>
>>>>> Extensions are a great feature and I’m really glad that Swift has
>>>>> them. Conflicts should be handled by improving import and disambiguation
>>>>> features like Xiaodi says which is useful for other cases as well.
>>>>>
>>>>> -Thorsten
>>>>>
>>>>>
>>>>> Am 18.04.2017 um 03:47 schrieb Xiaodi Wu via swift-evolution <
>>>>> swift-evolution at swift.org>:
>>>>>
>>>>> Simple: you put the user code that needs the more accurate library in
>>>>> one file and import only the more accurate library there, and you put the
>>>>> user code that needs the more performant library in a separate file and
>>>>> import only the more performant library there! Swift's devotion to
>>>>> file-based organization presents endless avenues of flexibility!
>>>>>
>>>>> What you write is an argument for designing a more expressive import
>>>>> and/or disambiguation feature, not for disallowing public retroactive
>>>>> conformance. A proposal to rip out entirely the ability to vend public APIs
>>>>> with extensions is simply not going to fly. The core team has committed to
>>>>> a small standard library and multiple independent core libraries like
>>>>> Foundation. This can only work because Foundation can greatly expand the
>>>>> API of standard library types through extensions.
>>>>>
>>>>> Your proposal would undo that design decision and require folding
>>>>> Foundation's functionality into the standard library, or rewriting the
>>>>> entire Foundation overlay to encapsulate standard library types instead of
>>>>> extending them. For example, NSString would have to be a separate type that
>>>>> encapsulates String. Yet oodles of work have gone into making NSString
>>>>> seamlessly bridge to String in the first place.
>>>>> On Mon, Apr 17, 2017 at 20:05 Howard Lovatt <howard.lovatt at gmail.com>
>>>>> wrote:
>>>>>
>>>>>> Comments in-line below
>>>>>>
>>>>>> -- Howard.
>>>>>>
>>>>>> On 17 Apr 2017, at 9:01 am, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>>>>
>>>>>> This continues to forbid use cases that are critical.
>>>>>>
>>>>>>
>>>>>> I think "critical" is overstating the importance. Plenty of
>>>>>> successful languages do not have extensions. Extensions have been discussed
>>>>>> and rejected by successful languages. The .NET guidelines suggest
>>>>>> considered cautious use. I have tried to encapsulate the best practice into
>>>>>> a language feature.
>>>>>>
>>>>>>
>>>>>> For instance, I am writing a library that vends additional
>>>>>> conformances for Float and Double. Any numerics library would need to do
>>>>>> the same.
>>>>>>
>>>>>>
>>>>>> You need to consider this carefully because your numerics library
>>>>>> might add a method sinh for example and the user of your library might be
>>>>>> using other numerical libraries as well, one of these others might also
>>>>>> provide sinh. Which is to be called in the user code by f.sinh? Suppose one
>>>>>> library emphasises speed over accuracy and the other vice versa. You really
>>>>>> want access to both versions in the user code. This is a situation I have
>>>>>> come across a few times in numeric C, Java, and C++ with matrix libraries
>>>>>> where code I have worked on has used multiple libraries in the same
>>>>>> application for good reason.
>>>>>>
>>>>>> I think you would be better vending functions for things like sinh,
>>>>>> rather than extending float with an additional function, and vending types
>>>>>> for more complex things like matrices, rather than extending arrays with
>>>>>> dot products for example. If you vend a type you can easily give access to
>>>>>> the underlying type using composition rather than extension or inheritance,
>>>>>> there is an example of this in the proposal just above the Justification
>>>>>> section..
>>>>>>
>>>>>>
>>>>>> 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.
>>>>>>
>>>>>> I am trying to make Swift more consistent, easier to learn, and to
>>>>>> encourage third-party libraries.
>>>>>>
>>>>>>
>>>>>> 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
>>>>>>>
>>>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>
>>>>> --
>>>> -- Howard.
>>>>
>>> _______________________________________________
>> 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/20170503/f813856a/attachment.html>
More information about the swift-evolution
mailing list