This was the conversation we were having in the other thread. Perhaps I'm still not understanding something, but I'm not convinced that this feature is an improvement.
Currently, protocols represent a declaration that you have discovered that your type has certain semantics and guarantees a certain API, and you declare that fulfillment "by name only," just as you invoke members "by name only"; unintentional failure to fulfill the contract is a compile-time error. The status quo is precisely my idea of what protocols should be. What are the two ideas you have about them? On Tue, Sep 20, 2016 at 18:02 Karl <[email protected]> wrote: > I’m using String as an example of where this issue of conformance > conflicts crops up in the standard library. Ideally, String (or any data > type) should be able to conform to protocols whose requirements have > conflicting names. > Currently, in order to work around this limitation, you have to delegate > the conformance to a supporting type. This is more complicated to write and > maintain, and pollutes your internal API. String gives us an example of > this, but it’s not the worst example. > It basically implements what I’m talking about anyway, but manually via a > supporting type instead of directly inside the data-type (String). As an > ABI consideration, we should scope all protocol members to their protocols. > This would resolve all naming conflicts. > > I can’t understand how anybody would argue for the status quo - we’re > currently in-between two ideas of what protocols should be - they are > explicit but their conformances are resolved by name only and can overlap > without warning. > > On 21 Sep 2016, at 00:48, Xiaodi Wu <[email protected]> wrote: > > Sorry, I'm still not sure I understand what you're getting at about this. > How would String conforming to Collection multiple times simplify or > improve the implementation of String? Or are you arguing it would be better > for users of String? If so, how? > > On Tue, Sep 20, 2016 at 17:26 Karl <[email protected]> wrote: > >> I’m not saying vital support is missing, just that it is more awkward. >> String doesn’t conform to collection, String.UTF8View does; so if you >> change some implementation detail (in StringCore, because that’s where they >> live for String), you get the error popping up somewhere other than the >> place you just changed. That’s what I mean when I say “language support”. >> If you do what StringCore does, and you’re changing stuff which is >> ultimately going to be used to conform to, say, Collection, you have to >> build distance between the implementation and the (only) conformance it is >> used for, and it’s less optimal. >> >> Let’s say I have an object MyComplexDataType, it implements >> “InternalStructureView” and “Collection”: >> >> ``` >> protocol InternalStructureView { >> associatedtype Index >> var count : Int { get } // whatever, just some stuff that will cause >> a name conflict >> func doInternalMagic(at: Index) >> } >> >> struct MyComplexDataType { >> var __collection_count : Int { >> // This is quite a complex operation which we’d rather leave >> inside the type, otherwise we’d need to expose a bunch of implementation >> details internally >> } >> var __internal_count : Int { >> // Again, best left here >> } >> >> struct CollectionView : Collection { >> init(parent: MyComplexDataType) { … } >> var count { return parent.__collection_count } >> // ...etc >> } >> >> struct InternalStructure : InternalStructureView { >> init(parent: MyComplexDataType) { … } >> var count { return parent.__internal_count } >> // ...etc >> } >> >> var collection : CollectionView { return CollectionView(self) } >> var internalStructure : InternalStructure { return >> InternalStructure(self) } >> } >> ``` >> >> This is basically what String does (except that it wants to conform to >> Collection multiple times with different indexes and results). It’s a lot >> of work to maintain, especially if you have evolving protocols that are >> conformed to in several places. >> We should have a better solution. We should be able to define: >> >> “protocol UTF8Collection : Collection {} >> protocol UTF16Collection : Collection {}” >> >> and have String conform to both of them at the same time. At the same >> time, since we’re now being explicit about which protocol requirement is >> satisfied where - we should also be able to delegate our conformance, >> telling the compiler to dispatch any unimplemented methods to another >> object. For example, lets say you want to wrap a Collection and observe >> mutations to it; you might override replaceSubrange(), but every other >> method (such as count, index(after:)… all the rest) is just a forwarding >> function. >> >> Interestingly, we could do this safely (from a code legibility >> perspective) if we say that every scope which adds a conformance must >> completely satisfy its requirements, which we would more reasonably be able >> to require if we made this change (so your internal functions can still be >> wherever you like, but they won’t automatically satisfy a protocol >> requirement if they happen to have the same name). Then we could reasonably >> say that if you add an extension which adds a conformance to, say, >> Collection, you have to tell us where to find every one of its >> requirements. That’s where we could put the forwarding syntax for >> retroactive modelling. Stored properties can’t be defined in extensions, so >> if you want to back an implementation with one, you’ll need to make its >> conformance explicit in the main body (or we loosen that to at least >> extensions in the same file). >> >> ``` >> // generates thunks to members of this extension in the base type; >> // so MyComplexDataType.count —> >> MyComplexDataType.InternalStructure.count, >> // to account for conformance being added in later version. Can also be >> used for renamed protocols, and be tagged on individual members. >> >> @makeAvailable(as: _) >> extension MyComplexDataType : InternalStructure { >> >> typealias Index = InternalIndexType >> var count : Int { >> // We have access to all of the private members because we’re >> inside MyComplexDataType >> // No need to pollute internal API with conformance >> implementation details. >> // Also, we get errors about non-conformance where we want them — >> where the implementation is. >> } >> func doInternalMagic(at: Index) { >> ... >> } >> } >> ``` >> >> On 20 Sep 2016, at 23:46, Xiaodi Wu <[email protected]> wrote: >> >> I'm not sure I understand. What compiler or language support is missing >> for StringCore? >> On Tue, Sep 20, 2016 at 16:42 Karl via swift-evolution < >> [email protected]> wrote: >> >>> On 20 Sep 2016, at 23:28, Karl <[email protected]> wrote: >>> >>> >>> On 20 Sep 2016, at 18:43, Nevin Brackett-Rozinsky via swift-evolution < >>> [email protected]> wrote: >>> >>> I have been following this discussion (as well as similar threads >>> earlier this year) and listening to the ideas put forth by all sides. >>> >>> It seems to me that the fundamental difference between classes and >>> protocols is that classes inherit implementation whereas protocol >>> conformance is a promise about interface. >>> >>> When a class or struct or enum declares itself as conforming to a >>> protocol, that means it has all the members specified in the protocol. The >>> protocol conformance simply codifies a fact about the type itself: namely >>> that all those members are present. >>> >>> In this model, any keyword such as `implements` on each conforming >>> member would introduce substantial boilerplate for negligible gain. The >>> purpose of a protocol is to communicate that certain members are available, >>> not to make declaring those members more onerous. >>> >>> However, default implementations for protocols blur the line. Now there >>> is actual implementation being inherited. A conforming type may choose to >>> roll its own version of a method, or to utilize the default provided by the >>> protocol. This is closer to the situation with subclassing. >>> >>> Moreover, a protocol which conforms to another protocol may itself >>> define (or redefine!) default implementations for members of that other >>> protocol. This can create “inheritance chains” of protocol default >>> implementations. I think there is value in being able to refer to (and >>> call) the inherited default implementation through some sort of `super` >>> functionality. >>> >>> On the other hand, the existence of a default implementation in a >>> protocol is in large part merely a convenience: a courtesy so that each >>> conforming type need not rewrite the same boilerplate code. >>> >>> A type which conforms to a protocol may accept the default or it may >>> provide its own implementation, but it is not “overriding” anything. The >>> default implementation was offered as a convenience, to be taken or left as >>> needed. Thus I do not think any keyword (neither `override` nor >>> `implements`) should be required in that case either. >>> >>> The frequently-raised point regarding near-miss member names deserves >>> some attention. Several people have expressed a desire for the compiler to >>> assist them in determining whether a given member does or does not meet a >>> protocol requirement. Specifically, when a type conforms to a protocol with >>> a default implementation, and the type defines a member with a similar >>> signature, it is not obvious at glance if that member matches the protocol. >>> >>> I think this is a job for linters and IDEs. For example, syntax >>> highlighting could distinguish members which satisfy a protocol >>> requirement, thereby providing immediate visual confirmation of success. >>> >>> Having followed the lengthy discussion and weighed the numerous ideas >>> put forth, I come down firmly on the side of no keyword for protocol >>> conformance. >>> >>> A protocol describes an interface and provides a set of customization >>> points. It may also, as a convenience, offer default implementations. The >>> protocol simply describes the capabilities of its conforming types, and any >>> default implementations are there to make things easier for them. >>> >>> Conforming types should not be afflicted with extraneous keywords: that >>> would run contrary to the purpose of having protocols in the first place. >>> >>> Nevin >>> >>> >>> On Tue, Sep 20, 2016 at 11:16 AM, Xiaodi Wu via swift-evolution < >>> [email protected]> wrote: >>> >>>> As I mentioned above, I agree that better diagnostics for near-misses >>>> are necessary, but they are possible without new syntax. There is no win in >>>> avoiding unintentional behavior because, without a default implementation, >>>> these issues are caught at compile time already. >>>> >>>> On Tue, Sep 20, 2016 at 10:14 Vladimir.S via swift-evolution < >>>> [email protected]> wrote: >>>> >>>>> >>>>> > extension P { >>>>> > implement func foo() -> [String : String] { return [:] } >>>>> > } >>>>> >>>>> Yes, it seems like we need `implement` (or `override` as another >>>>> suggestion) in protocol extension also just for the same reasons - be >>>>> clear >>>>> about our intention regarding implementing the requirement, to show >>>>> that >>>>> this func *depends* on the previous definition of P protocol and to >>>>> avoid >>>>> possible mistakes related to protocol conformance. >>>>> >>>>> On 20.09.2016 17:38, Charles Srstka wrote: >>>>> >> On Sep 20, 2016, at 8:17 AM, Vladimir.S via swift-evolution >>>>> >> <[email protected] <mailto:[email protected]>> >>>>> wrote: >>>>> >> >>>>> >> On 20.09.2016 3:03, Xiaodi Wu via swift-evolution wrote: >>>>> >>> I definitely think Vladimir's suggestion is a great starting >>>>> point, IMO. >>>>> >>> >>>>> >>> However, I think it could be improved in one key respect where >>>>> previous >>>>> >>> proposals using `override` are superior. Namely, the proposed >>>>> `implement` >>>>> >>> keyword adds no additional safety when a type implements a protocol >>>>> >>> requirement that doesn't have a default implementation. This is >>>>> because, if >>>>> >> >>>>> >> Yes, *at the moment of writing* the type's code there could be no >>>>> default >>>>> >> implementation for protocol requirement. But, *at the moment of >>>>> >> compilation* such default implementation could appear. >>>>> >> >>>>> >> Let's discuss such scenario in case we'll take your suggestion: >>>>> >> >>>>> >> You got SomeClass.swift file, 3rd party file you don't want to >>>>> change or >>>>> >> changes are not allowed. Content: >>>>> >> >>>>> >> public protocol SomeProtocol { >>>>> >> func foo() >>>>> >> } >>>>> >> >>>>> >> public class SomeClass : SomeProtocol { >>>>> >> func foo() {...} // no default implementation *at the moment of >>>>> writing*, >>>>> >> no need in `overload` >>>>> >> } >>>>> >> >>>>> >> Now, you adds SomeClass.swift file to your project and in some >>>>> *other* >>>>> >> file you write: >>>>> >> >>>>> >> extension SomeProtocol { >>>>> >> func foo() {...} >>>>> >> } >>>>> >> >>>>> >> As you see, you don't control the SomeClass.swift but you suggest >>>>> in this >>>>> >> case SomeClass.foo() should be defined with `override`. >>>>> >> >>>>> >> With 'implement' SomeClass.foo() will be marked initially and will >>>>> save >>>>> >> us if protocol's requirement PLUS default implementation changed. >>>>> > >>>>> > Requiring the ‘implement’ keyword can help us even if no default >>>>> > implementation is involved. Consider: >>>>> > >>>>> > protocol P { >>>>> > func foo() -> [String : Any] >>>>> > } >>>>> > >>>>> > struct S : P { >>>>> > func foo() -> [String : String] { return [:] } >>>>> > } >>>>> > >>>>> > We will get an error here that S does not conform to P. However, >>>>> this is >>>>> > not the correct error, since S in fact *tries* to conform to P, but >>>>> it has >>>>> > a mistake in a method signature. This misleads us as to the true >>>>> nature of >>>>> > the problem, and if S has enough members in it that we fail to spot >>>>> the >>>>> > existing foo(), we might solve the problem by reimplementing foo(), >>>>> and >>>>> > leaving the original foo() as dangling dead code. Having an >>>>> ‘implement’ >>>>> > keyword on the existing foo() function would change the compiler >>>>> error to >>>>> > let us know that we have an existing foo() that is incorrectly >>>>> declared. >>>>> > >>>>> > In addition, ‘implement’ can help us when the declaration in >>>>> question *is* >>>>> > the default implementation: >>>>> > >>>>> > protocol P { >>>>> > func foo() -> [String : Any] >>>>> > } >>>>> > >>>>> > extension P { >>>>> > implement func foo() -> [String : String] { return [:] } >>>>> > } >>>>> > >>>>> > Here we will get an error with the proposed ‘implement’ keyword, >>>>> because >>>>> > foo() does not have a signature matching anything in the protocol, >>>>> whereas >>>>> > without ‘implement’ we would happily and silently generate a useless >>>>> > dangling function that would never be used, and then pass the buck >>>>> to the >>>>> > concrete type that implements P: >>>>> > >>>>> > protocol P { >>>>> > func foo() -> [String : Any] >>>>> > } >>>>> > >>>>> > extension P { >>>>> > func foo() -> [String : String] { return [:] } // The error is here: >>>>> > } >>>>> > >>>>> > struct S : P {} // But it gets reported here. >>>>> > >>>>> > Charles >>>>> > >>>>> _______________________________________________ >>>>> swift-evolution mailing list >>>>> [email protected] >>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>> >>>> >>>> _______________________________________________ >>>> swift-evolution mailing list >>>> [email protected] >>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>> >>>> >>> _______________________________________________ >>> swift-evolution mailing list >>> [email protected] >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> >>> >>> >>> I agree that a new keyword is unwanted. Conforming to protocols is quite >>> a common thing, so you want it to be easy to remember. >>> >>> I think the best way is to prefix the member name with the protocol, e.g: >>> >>> protocol MyProto { >>> var aVariable : Int >>> func aFunction() >>> } >>> class MyClass : MyProto { >>> var MyProto.aVariable : Int >>> func MyProto.aFunction() { … } >>> } >>> >>> This is consistent with how we refer to other members of types (e.g. >>> “extension MyClass.MyInternalClass”). It will be easy for autocompletion to >>> provide good suggestions, too. >>> As I see it, the only problem is what if `MyClass` wants its own >>> function called `aFunction()`? What if the same name satisfies 2 protocols, >>> which do you write? >>> >>> The way to solve all of the problems in a consistent way is to make the >>> function actually called “MyProto.aFunction”, and for it to be a separate >>> function from plain “aFunction()” or from “SomeotherProto.aFunction”. >>> >>> I believe it is crucial to protocols that we can do this. Maybe I have >>> some complex data structure and it has its own API, but I want people to be >>> able to view it as a Collection. By conforming to Collection, I reserve >>> lots of keywords and indexing operations which I now can’t use in my own >>> API. Maybe I’m just providing Collection as a convenience to work with >>> generic algorithms, but my own API has more efficient semantics for some >>> operations. We’re relegated to using less-obvious and legible names in >>> order to avoid conflicts. >>> >>> We have a way to work around this, which String uses - create a struct >>> which references your object and calls internal methods such as >>> “_collection_count” so you can have separate interfaces. This adds up to >>> quite a lot of boilerplate and maintenance overhead. >>> >>> >>> Also to add here: you’re basically implementing what I’m proposing >>> manually if you do this; only you don’t get language/compiler support. >>> String basically does this - it shares StringCore with UTF8View and >>> defines some internal functions to support it. >>> >>> The String views could then be made in to protocols on String, turning >>> “UTF8View” in to “UTF8Representable”, and opening up algorithms which can >>> work on generic sequences of UTF8 bytes. I think that’s pretty cool, and >>> could open up better integration with other types which are (for example) >>> UTF8Representable — for example a stream of UTF8 bytes (depending on how >>> flexible implementation allows us to make the protocol). >>> >>> >>> I don’t agree that Protocol conformances are kind-of incidental, as >>> others here have written. This isn’t like Objective-C where anything that >>> has the correctly-named methods conforms. Protocol conformances are >>> completely explicit, and in fact we have empty protocols (“marker >>> protocols”) for exactly that purpose. I think it is consistent that we make >>> every member of a conformance specify which protocol it belongs to, and to >>> have its name scoped to that protocol. >>> >>> Karl >>> >>> >>> CC-ing Dave A, to understand better if this fits with the vision of >>> protocols >>> >>> _______________________________________________ >>> swift-evolution mailing list >>> [email protected] >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> >> >> >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
