[swift-users] override-like keyword for default implementations
Howard Lovatt
howard.lovatt at gmail.com
Tue May 16 22:39:53 CDT 2017
Here is a proposal I put forwards last month that addresses the issue (and
others), the original versions did not allow retroactive modeling but
following feedback it was incorporated.
PS Bitten by this problem two days ago, copied some example Swift 2 code
into a Swift 3 project and an obj-c method had changed its name. Took ages
to find the culprit! The error was a null pointer exception it a completely
different bit of code! Very frustrating!
============================================================
=====================
# 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 i
nto 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 extensionadds
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/proposa
ls/0169-improve-interaction-between-private-declarations-and-extensions.md
>.
============================================================
=====================
-- Howard.
On 17 May 2017 at 07:41, Jordan Rose via swift-users <swift-users at swift.org>
wrote:
>
> On May 16, 2017, at 10:44, Charles Srstka via swift-users <
> swift-users at swift.org> wrote:
>
> On May 16, 2017, at 12:32 PM, Nevin Brackett-Rozinsky via swift-users <
> swift-users at swift.org> wrote:
>
>
> There is not.
>
> At some point in the future, I believe the plan is to eventually allow
> default implementations to be written in-line within the protocol
> declaration itself. In other words, the fact that default implementations
> currently must appear in extensions, is a temporary limitation that has not
> yet been a priority to address.
>
> Once we gain the ability to define default implementations within
> protocols themselves, rather than extensions, then your use-case will “just
> work” (at least as long as you control the protocol anyway). I wouldn’t
> hold my breath though, as that behavior will not appear this year, and the
> plans for next year have not been hashed out yet.
>
>
> Even that won’t completely solve the problem, though, because:
>
> protocol P {
> func foo() {
> // default implementation
> }
> }
>
> struct S: P {
> func foo() {
> // overriden implementation
> }
> }
>
> If foo is refactored here, S will start getting the wrong implementation
> of it, silently, with no warning.
>
> People have tried to bring up proposals to add some sort of
> “override”-like keyword for protocols on swift-evolution a bunch of times,
> but it always gets shouted down by certain members of the group, so we’re
> probably permanently stuck with this situation where a supposedly
> “protocol-oriented” language is not safe to use with protocols.
>
>
> I object to this characterization. People have tried to bring up such
> proposals, and it is invariably pointed out (usually by the same group of
> us) that these proposals don't discuss retroactive modeling, and then the
> thread dies. I can't remember a case where the proposal author actually
> incorporates this feedback into their proposal to handle retroactive
> modeling, or extension members of one protocol being used as the default
> implementation of another protocol.
>
> (This just means the problem space is more difficult than the proposer
> initially thought, and they're not going to take that on right now, which
> is fine. It's a learning experience; designing features that interact well
> with the whole language is not easy! We should probably add this to the
> "commonly proposed" list, though, so that we don't keep retreading that
> initial ground.)
>
> Jordan
>
>
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org
> https://lists.swift.org/mailman/listinfo/swift-users
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20170517/5da5bec7/attachment.html>
More information about the swift-users
mailing list