-- Howard.
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.
On Sun, Apr 16, 2017 at 17:51 Howard Lovatt <howard.lovatt@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, TIOBE Index - TIOBE 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.c
om/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/proposa
ls/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/proposa
ls/0169-improve-interaction-between-private-declarations-
and-extensions.md.
====================================
On 14 Apr 2017, at 8:17 am, Brent Royal-Gordon < >>>>>>>> brent@architechies.com> wrote:
On Apr 13, 2017, at 3:10 PM, Howard Lovatt via swift-evolution < >>>>>>>> swift-evolution@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
_______________________________________________