[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:13:14 CDT 2017


Two points:

  1. The proposal allows for final extensions that work as expected, same
as they would for a class.

  2. I would argue that the behaviour of `contains` is high undesirable and
should be changed (whether approved by Swift Evolution or not). I don't now
why it is the way it is, probably for expediency rather than desirability.
See below for how horrible the situation currently is:

// A sequence that can benefit from optimized implementation of its methods.
// In example just `contains` and `filter` optimised, but could be many
more methods.
struct SequenceOfOneInt: Sequence, IteratorProtocol {
    let value: Int
    private var read = false

    init(_ value: Int) { self.value = value }

    mutating func next() -> Int? {
        if read { return nil }
        read = true
        return value
    }

    func contains(_ x: Int) -> Bool {
        print("Optimised: ", terminator: "") // Note when optimised version
called
        if x == value { return true }
        return false
    }

    func filter(_ isIncluded: (Int) throws -> Bool) rethrows -> [Int] {
        print("Optimised: ", terminator: "") // Note when optimised version
called
        return try isIncluded(value) ? [value] : []
    }
}

let s = SequenceOfOneInt(3)

print(s.contains(3))            // Optimised: true
print(s.filter({ $0 < 5 }))      // Optimised: [3]

func sPrint<S: Sequence>(_ s: S) where S.Iterator.Element == Int {
    print(s.contains(3))
    print(s.filter({ $0 < 5 }))
}
sPrint(s)                       // true - i.e. optimised version not used!!!
                                    // Optimised: [3]

Surely this behaviour is not desirable, `filter` and `contains` are treated
identically in the code above and because of some vagary of implementation
of `contains` we get unwanted and very hard to debug behaviour.

The proposal would make both `contains` and `filter` behave as `filter` now
does, i.e. the desirable behaviour.


  -- Howard.

On 3 May 2017 at 10:44, Xiaodi Wu <xiaodi.wu at gmail.com> 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.
>
> 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.
>>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170503/3f9b1f73/attachment-0001.html>


More information about the swift-evolution mailing list