@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 <[email protected]> wrote:

>> On Apr 13, 2017, at 3:10 PM, Howard Lovatt via swift-evolution 
>> <[email protected]> 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
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to