[swift-evolution] Mark protocol methods with their protocol

Vladimir.S svabox at gmail.com
Mon Sep 19 12:10:46 CDT 2016

On 17.09.2016 6:32, Xiaodi Wu via swift-evolution wrote:
> Let me give a concrete example of how retroactively modeling is used.

Karl is suggesting interesting but complex and IMO too much code-breaking 
idea that I don't believe can be implemented at all in a reasonable amount 
of time to be a part of Swift as soon as possible, to address the discussed 
issue with protocols.

I wonder what objections could be made on the solution proposed below, 
which should solve a major(IMO) number of issues with protocol conformance 
and introduce only 1 keyword. Such solution will make Swift better as 
Protocol-Oriented language and later we can even improve it, but it can 
already solve a big number of issues:

1. As soon as possible we add 'implement' keyword which is required to mark 
method/property that was defined in type or extension exactly to conform to 
some protocol.

2. The 'implement' required only at a moment of 'direct' conformance, i.e. 
when you declare methods/props of the type/extension that explicitly 
conformed to protocol.

3. Retrospective conformance will not require this keyword and will work 
for now just like it is working today.

4. Later, if this will be possible at all, we can extend this model to 
support separate implementation of protocols with same requirements in the 
same type, explicit protocol name in implemented methods/props and 
improvements for retrospective conformance. For example some variants for 
*future* improvements:

4.1 Different implementation for different protocols
class Foo : ProtocolA, ProtocolB {
   implement(ProtocolA) func foo() {...}
   implement(ProtocolB) func foo() {...}
class Foo : ProtocolA, ProtocolB {
   implement ProtocolA {
  	func foo() {...}
   implement ProtocolB {
  	func foo() {...}

4.2 Retrospective conformance: What is the main problem with retrospective 
conformance? As I see it now(correct me, if I missing something), the 
problem arises in such situation:
* we *expect* that some method(s) in type will play the role of 
implementation of protocol's requirements, so we retrospectively conform 
that type to the protocol.
* but protocol has default implementation for its requirements
* and type's methods, that we *expect* to play roles for protocol 
implementation, has different parameters or slightly different method name 
at all.

I.e. when we have this set of code logic:

type T {
   func foo()

protocol P {
   func foo(x: Int)

extension P {
   func foo(x: Int) {...}

extension T : P { // expect foo in T will play role of P.foo

I support the opinion that it is not an option to require to explicitly 
list conformed methods/props in type extension for retrospective conformance.
But I do believe we need a way to *express our intention* regarding the 
retrospective conformance: do we expect that type already contains 
implementation for some protocol's requirements OR we are aware that 
protocol can have defaults for some methods and our type does not contains 
some implementations.

So, the solution here IMO is some syntax to express that intention. Right 
now I think that we can use current syntax "extension T : P" to keep it 
working as it now works: "I'm aware of all the names, defaults etc. Treat 
this as usually you did". But for example something like "extension T: 
implement P {..}" or "extension T: P(implement *) {..}" will say that we 
*expect* that all requirements of P protocol should be implemented inside T 
type. Or some syntax inside extension to specify the list of methods/props 
we expect to be implemented in T. Or "extension T : P(implement foo, 
bar(x:y:)) {..}".. Should be discussed.

But again, IMO this could be discussed later, after we'll have 'implement' 
for most important place - in type definition for method/prop that we 
created exactly for the conformed protocol.


> Currently, there is a JIRA bug that Set does not conform to SetAlgebra. To
> fix this issue, someone simply needs to write `extension Set : SetAlgebra {
> }` and some tests. That's literally what the bug (filed by a core team
> member) tells you to do. It's a starter bug, and if someone hasn't taken it
> yet, you the reader could have a go at it. What's neat about Swift is that
> it's super easy to provide the same functionality in your own project
> without waiting on that bug to be fixed in Swift itself. You can simply
> write a single line of code. By contrast, if your proposal were to be
> implemented, this would become much more difficult.
> This is actively used in Swift today. For example, in the Swift
> implementation of NSScanner, you'll find the following lines:
> ```
> internal protocol _BitShiftable {
>     static func >>(lhs: Self, rhs: Self) -> Self
>     static func <<(lhs: Self, rhs: Self) -> Self
> }
> internal protocol _IntegerLike : Integer, _BitShiftable {
>     init(_ value: Int)
>     static var max: Self { get }
>     static var min: Self { get }
> }
> extension Int : _IntegerLike { }
> extension Int32 : _IntegerLike { }
> extension Int64 : _IntegerLike { }
> extension UInt32 : _IntegerLike { }
> extension UInt64 : _IntegerLike { }
> ```
> If we adopted your proposed syntax below, it would take considerably more
> lines of boilerplate code to express the same thing. The burden increases
> significantly with the complexity of the retroactive modeling. For
> instance, if the retroactively modeled protocol had 20 requirements and you
> were retroactively conforming 20 types, that'd be at least 400 lines of
> boilerplate.
>     Basically, the way I see it, if my class MyClass implements MyProtocol,
>     providing someRequiredFunc(), there’s an “ownership” chain there
>     (reading it backwards).
>     Now what happens if MyClass implements MyOtherProtocol, which also has
>     someRequiredFunc()? In that case, I want to MyClass as a
>     MyOtherProtocol and get another function pointer, which just happens to
>     have the same human-readable name as some other property. Just because
>     they have the same function signature, absolutely doesn’t mean they’re
>     the same thing.
>     Now, if we strongly bind all protocol conformances to the protocol they
>     implement, what happens to instance methods? They don’t belong to any
>     protocol, their parent is the class itself. If you have an instance
>     method called someRequiredFunc(), and you later add a conformance to
>     MyProtocol, you would need to declare that it belongs to MyProtocol. If
>     you don’t want it to be an API-breaking change, you have to provide a
>     thunk (or we could provide a shorthand syntax which emits thunks for
>     you) to let us know that MyClass::someRequiredFunc() is the same thing
>     as MyClass::MyProtocol::someRequiredFunc().
> Your argument is that two methods with the same name should not in any way
> conflict with each other. This is a fundamental change from the status quo.
> If we were to take your argument to its logical conclusion, any member A of
> a type T should be capable of being designated as the implementation of a
> requirement B of protocol P. In the most general case, two functions A and
> B shouldn't even need to take the same number of arguments, or arguments of
> the same type; you should be able to supply default arguments, or even
> write custom code that takes arguments for A and computes suitable
> arguments for B in order to forward A to B, and the language should allow
> you to designate A as an implementation of B. But that is simply not how
> Swift protocols are designed.
>     Let’s take an example where retroactive modelling could go wrong.
>     You’ve got different teams working on different parts of an App, and
>     they’ve all got their own convention for “copy()”. Sometimes it’s a
>     deep-copy, sometimes a shallow-copy, sometimes it’s used in a fragile
>     way for a specific case, whatever. Now you want to go and clean that up
>     by creating a “Copyable” protocol with codified guarantees. Some
>     objects may already conform, others may need tweaks, and some may want
>     both behaviours simultaneously (preserving the old,
>     non-Copytable-compliant behaviour until the next API break), depending
>     on how you look at the object. A system like this allows all of those
>     different ways of looking at the object live together. You could have
>     the old, non-comforming API as an extension with a FIXME to delete it
>     for version 2.
> Even if you design a protocol called Copyable, you still need to explicitly
> extend concrete types in order to conform to Copyable. Swift does not
> automagically make anything conform to your protocol. If you choose
> *explicitly* to conform different types that don't guarantee the same
> semantics, and then you erroneously assume that they all have the same
> semantics even though you *explicitly* chose types that don't have the same
> semantics, you're the one who shot yourself in the foot, so to speak. It's
> not the fault of Swift at all.
>     I think it’s pretty arcane that members of a type are resolved only by
>     their names. If you want to provide types which allow flexible views of
>     data, each view of that data needs to be completely free in its
>     expressivity.
>     I would actually like to see a syntax like:
>     ```
>     let testObject = MyClass()
>     let testMyProto = testObject.MyProtocol // the protocol-witness table
>     for testObject as a MyProtocol.
>     testObject.MyProtocol.someRequiredFunc() // that’s one function
>     testObject.someRequiredFunc() // is a different function. May happen to
>     have the same implementation as above if MyProtocol was retroactively
>     modelled.
>     ```
>     I think it would fit well with the dispatch system for protocol
>     extensions, too. I sometimes have code like the following:
>     ```
>     protocol Base {}
>     protocol Derived : Base {}
>     extension Base {
>       func doSomething() { … }
>     }
>     extension Derived {
>       func doSomething() {
>>        (self as Base).doSomething() // Would be better if we could say
>     “self.Base.doSomething()” to disambiguate instead of casting.
>       }
>     }
>     ```
> This is a complete redesign of protocols in Swift. With the emphasis on
> minimizing source-breaking changes, I doubt such a change would be in scope
> for any phase of Swift unless you could show an overwhelming benefit.
>     So yeah, a big +1 to marking protocol methods with their protocol
>     (whatever the syntax ends up looking like), and actually I’d take it
>     further and bake them in to the ABI. That also makes it relevant for
>     Swift 4 phase 1.
>     Karl
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

More information about the swift-evolution mailing list