[swift-evolution] Mark protocol methods with their protocol

Xiaodi Wu xiaodi.wu at gmail.com
Fri Sep 16 22:32:13 CDT 2016


On Fri, Sep 16, 2016 at 20:28 Karl <razielim at gmail.com> wrote:

> On 17 Sep 2016, at 01:45, Xiaodi Wu via swift-evolution <
> swift-evolution at swift.org> wrote:
>
> I absolutely agree that it's a problem worth solving. However, the
> question is whether a particular proposed design solves the problem and
> avoids previously stated weaknesses. What I'm saying here is that, so far,
> the conversation in this thread has involved reiterating already-proposed
> designs that have been critiqued. It's really quite tiring for me too to
> repeat the same critique when someone raises the same proposal a second or
> third time.
>
> It's possible that it makes sense to have a separate syntax for
> retroactive modeling. I haven't been able to come up with one that seems
> reasonable to me, or I would have written to this list to propose it. Do
> you have such a design in mind?
> On Fri, Sep 16, 2016 at 16:59 Charles Srstka <cocoadev at charlessoft.com>
> wrote:
>
>> On Sep 16, 2016, at 4:08 PM, Xiaodi Wu via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>>
>> We've had this discussion on the list multiple times already. The gist of
>> the difficulty here is that most proposals for a mandatory keyword don't
>> permit retroactive modeling, so it's a no-go. On the other hand, the core
>> team seems to take a dim view to optional syntax, since that's more in the
>> ballpark of linters.
>>
>>
>> Numerous solutions to your objection have been proposed; you always
>> simply dismiss all of them in favor of your dogmatic stance. It’s really
>> quite tiring. You can have this and support retroactive modeling; you just
>> might need to have a separate syntax for retroactive conformances. You keep
>> bringing that up as a hard-and-fast objection, but you know what? Maybe
>> retroactive conformances *should* have a separate syntax, because
>> they’re not saying the same thing! One is saying "here are some methods
>> that will make this type conform to this protocol”, where the other is
>> saying “this type already has the methods that conform to this protocol
>> somewhere.” These are not the same thing, and it might be confusing to see
>> a conformance declaration and assume it’s the former when it’s actually the
>> latter, and then have trouble finding the conformances. Maybe it would
>> actually make your code clearer if retroactive conformances were required
>> to declare “this method exists somewhere else already.” Maybe you could
>> even command-click on it and jump to the actual declaration. Anything would
>> be better than the current situation, because:
>>
>> The reason this keeps coming up is because it’s a real problem. I myself
>> have started taking up the practice of always using copy-and-paste to
>> declare conformances to protocols, because otherwise the chances of
>> mistyping something and having the bug not manifest itself until runtime is
>> simply too high. This is not a “linter” problem; this affects basic
>> functionality and makes protocols, honestly, really dangerous to use. For a
>> language that bills itself as “protocol-oriented”, it’s really quite
>> damning that its protocol support is this brittle and fragile compared to
>> its support for traditional inheritance. I’ve been bitten by this enough
>> times by now to somewhat regret the decision to go with a protocol-based
>> design. This is a real shame because conceptually, the idea of Swift’s
>> protocol-based design is really cool.
>>
>> Charles
>>
>> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
> I don’t see what the big problem about retroactive modelling is.
>

Let me give a concrete example of how retroactively modeling is used.
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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160916/b1ca7a29/attachment.html>


More information about the swift-evolution mailing list