> On 17 Sep 2016, at 05:32, Xiaodi Wu <xiaodi...@gmail.com> wrote:
> 
> On Fri, Sep 16, 2016 at 20:28 Karl <razie...@gmail.com 
> <mailto:razie...@gmail.com>> wrote:
>> On 17 Sep 2016, at 01:45, Xiaodi Wu via swift-evolution 
>> <swift-evolution@swift.org <mailto:swift-evolution@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 <cocoa...@charlessoft.com 
>> <mailto:cocoa...@charlessoft.com>> wrote:
>> On Sep 16, 2016, at 4:08 PM, Xiaodi Wu via swift-evolution 
>> <swift-evolution@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>> <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.
>  

Yes, this is necessarily going to be a big chunk of boilerplate, because there 
are n integer types with no useful common ancestors. We could come up with all 
kinds of shorthands for making this easier, but there are basically two 
situations when adding a conformance of a new protocol P when there are 
existing members with the same names:

- You’re adapting the semantics of new protocol P to your internal way of 
working. You need a more advanced bridge between the two models which may 
require maintenance as your internal workings change. Nothing much we can do 
about that.
- You’re ensuring capabilities which may be provided by other protocols. You 
want to forward to the implementation of P.member to your an existing 
protocol’s X.member because they have the same semantics.

Ideally, you would use a protocol extension for the latter case. It would say 
that “all types which conform to StandardLibraryBitShiftable conform to 
_BitShiftable”. In this case, as we don’t have protocol-oriented Integers, and 
the current design makes this one particular case really annoying. We could 
probably have a shorthand syntax for this sort of case, too. I’m not worried 
about that at all; the important thing is that we get the semantics correct and 
that the model makes Swift types even more powerful. We can worry about 
reducing boilerplate later, because in theory it’s all about hooking up the 
correct protocol-witness table with the correct function pointer and we’ll have 
all the information to do that.


> 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.

Yes, that’s my argument. Changing the status quo is basically what swift-evo is 
_for_.

And yes, you’ve basically got the concept - you should connect up the protocol 
requirement <-> implementation table by manually marking the protocol name on 
them (as the proposal says). Migration would be straight-forward.
But actually, I do think this fits with how Swift protocols are designed. In 
Objective-C, anything that responds to the same selectors as NSCopying conforms 
to NSCopying, because Objective-C’s dispatch works by selector names. In Swift, 
as you point out later, you need to manually declare that conformance even if 
it responds to the equivalent of “-(id) copyWithZone:”. A Swift protocol is a 
guarantee of a certain kind of behaviour, not just of functions with certain 
names. 

The status quo is that we are stuck in the middle - members are disambiguated 
only by their name, but just having the name isn’t enough for conformance. So 
when you come across a conflict in names, you need to rename all uses of one of 
them. If the protocol comes from a source you cannot change, you may be stuck 
in a situation where you *cannot conform to the protocol* and have to write 
your own wrapping thunk-er which does. Basically, you end up looking with code 
that looks like my proposal.

>  
> 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.
> 

Yes, and the best bit about this is that you’d need to explicitly add it to 
every would-be member of Copyable too, so you’d have to check that the new 
semantics you are imposing with the conformance are correct (unless using some 
retroactive modelling shorthand which tells the compiler to emit thunks for all 
members of MyClass->Copyable in to MyClass). That’s the right thing to do, IMO 
(and also, would be the perfect cue for Xcode’s rename tool to start supporting 
Swift…).

> 
> 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.

Not really a complete redesign - it fits with the semantic model of protocols 
which has already been established. I expect that protocols are going to change 
a lot in Swift 4 anyway, what with conditional conformances and better 
existentials, they’re going to be way more powerful than Swift 3 protocols. It 
is important we unlock full expressivity to allow them to be fully exploited, 
so we have to tackle the naming-conflict problem.

Also, I’m not sure source-breaking changes are such a big deal since we decided 
to support Swift 3 syntax. We could still support that - if you write 
“MyClass.someRequiredFunc()” which only exists inside a protocol, the compiler 
can search for the correct one to use.

There are great ways we can extend this, too - if you’ve got the ability to 
disambiguate the protocol witnesses yourself, perhaps you could override the 
“MyProtocol” witness getter and return an optimised wrapper which conforms to 
the protocol on your behalf. That would mean that all of the various String 
views (UTF8/16/Scalars/Characters) could be implemented as protocols on the 
single String type. So now suddenly we have a standard protocol for “a 
collection of UTF code points” which String is able to conform to, so it’s much 
easier to write generic code works with String and my own custom data-type 
which is also representable as some UTF code points.

I think that’s a pretty big win. It would allow us incredible new flexibility 
to model, which is ultimately what protocols are there to do.
It’s not even such a big change **at a high level** (I’m aware that, especially 
the first one, would touch just about every component of the compiler and 
standard library):

- Protocol conformances own their members; Members which are part of a protocol 
conformance no longer belong to their enclosing type directly.
- Synthesise getter for “.ProtocolName”, which returns the protocol witness
- (Optional) Make overridable to supply your own protocol witness
- Insert jumps to allow for retroactive modelling, with shorthand syntax

I’m not sure if it’s really a non-starter though. It would make the language 
much better and it would be pretty easy to migrate existing code (since we 
don’t allow non-unique names right now). Like I said, I expect the Swift 4 
standard library (with conditional conformances) to look very different to 
Swift 3 at any rate.

> 
> 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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to