It’s good to see this starting to happen!

Is the decision on "no-overlapping-conformances” something that’s seen-as set 
in stone permanently, set in stone for the near future, or perhaps at least 
somewhat open to reconsideration at the present moment?

> On Sep 26, 2016, at 7:18 PM, Douglas Gregor via swift-evolution 
> <swift-evolution@swift.org> wrote:
> 
> Conditional conformances
> 
> Proposal: SE-NNNN 
> <https://github.com/DougGregor/swift-evolution/blob/conditional-conformances/proposals/NNNN-conditional-conformances.md>
> Author: Doug Gregor <https://github.com/DougGregor>
> Review Manager: TBD
> Status: Awaiting review
> During the review process, add the following fields as needed:
> 
> Decision Notes: Rationale 
> <https://lists.swift.org/pipermail/swift-evolution/>, Additional Commentary 
> <https://lists.swift.org/pipermail/swift-evolution/>
> Bugs: SR-NNNN <https://bugs.swift.org/browse/SR-NNNN>, SR-MMMM 
> <https://bugs.swift.org/browse/SR-MMMM>
> Previous Revision: 1 
> <https://github.com/apple/swift-evolution/blob/...commit-ID.../proposals/NNNN-filename.md>
> Previous Proposal: SE-XXXX 
> <https://github.com/DougGregor/swift-evolution/blob/conditional-conformances/proposals/XXXX-filename.md>
>  
> <https://github.com/DougGregor/swift-evolution/tree/conditional-conformances#introduction>Introduction
> 
> Conditional conformances express the notion that a generic type will conform 
> to a particular protocol only when it's type arguments meet certain 
> requirements. For example, the Array collection can implement the Equatable 
> protocol only when its elements are themselves Equatable, which can be 
> expressed via the following conditional conformance on Equatable:
> 
> extension Array: Equatable where Element: Equatable {
>   static func ==(lhs: Array<T>, rhs: Array<T>) -> Bool { ... }
> }
> This feature is part of the generics manifesto 
> <https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#conditional-conformances->
>  because it's something that fits naturally into the generics model and is 
> expected to have a high impact on the Swift standard library.
> 
> Swift-evolution thread: TBD: Discussion thread topic for that proposal 
> <https://lists.swift.org/pipermail/swift-evolution/>
>  
> <https://github.com/DougGregor/swift-evolution/tree/conditional-conformances#motivation>Motivation
> 
> Conditional conformances address a hole in the composability of the generics 
> system. Continuing the Array example from above, it's always been the case 
> that one could use the == operator on two arrays of Equatable type, e.g., 
> [Int]() == [Int]() would succeed. However, it doesn't compose: arrays of 
> arrays of Equatable types cannot be compared (e.g.,[Int] 
> <https://github.com/DougGregor/swift-evolution/blob/conditional-conformances/proposals>==
>  [Int] 
> <https://github.com/DougGregor/swift-evolution/blob/conditional-conformances/proposals>will
>  fail to compile) because, even though there is an==for arrays of 
> Equatabletype, the arrays themselves are neverEquatable`.
> 
> Conditional conformances are particularly powerful when building generic 
> adapter types, which are intended to reflect the capabilities of their type 
> arguments. For example, consider the "lazy" functionality of the Swift 
> standard library's collections: using the lazy member of a sequence produces 
> a lazy adapter that conforms to the Sequence protocol, while using the lazy 
> member of a collection produces a lazy adapter that conforms to the 
> Collection protocol. In Swift 3, the only way to model this is with different 
> types. For example, the Swift standard library has four similar generic types 
> to handle a lazy collection: LazySequence, LazyCollection, 
> LazyBidirectionalCollection, and LazyRandomAccessCollection. The Swift 
> standard library uses overloading of the lazy property to decide among these:
> 
> extension Sequence {
>   var lazy: LazySequence<Self> { ... }
> }
> 
> extension Collection {
>   var lazy: LazyCollection<Self> { ... }
> }
> 
> extension BidirectionalCollection {
>   var lazy: LazyBidirectionalCollection<Self> { ... }
> }
> 
> extension RandomAccessCollection {
>   var lazy: LazyRandomAccessCollection<Self> { ... }
> }
> This approach causes an enormous amount of repetition, and doesn't scale well 
> because each more-capable type has to re-implement (or somehow forward the 
> implementation of) all of the APIs of the less-capable versions. With 
> conditional conformances, one can provide a single generic wrapper type whose 
> basic requirements meet the lowest common denominator (e.g., Sequence), but 
> which scale their capabilities with their type argument (e.g., the 
> LazySequence conforms to Collection when the type argument does, and so on).
> 
>  
> <https://github.com/DougGregor/swift-evolution/tree/conditional-conformances#proposed-solution>Proposed
>  solution
> 
> In a nutshell, the proposed solution is to allow a constrained extension of a 
> struct, enum, or class to declare protocol conformances. No additional syntax 
> is necessary for this change, because it already exists in the grammar; 
> rather, this proposal removes the limitation that results in the following 
> error:
> 
> t.swift:1:1: error: extension of type 'Array' with constraints cannot have an 
> inheritance clause
> extension Array: Equatable where Element: Equatable { }
> ^                ~~~~~~~~~
> Conditional conformances can only be used when the additional requirements of 
> the constrained extension are satisfied. For example, given the 
> aforementioned Array conformance to Equatable:
> 
> func f<T: Equatable>(_: T) { ... }
> 
> struct NotEquatable { }
> 
> func test(a1: [Int], a2: [NotEquatable]) {
>   f(a1)    // okay: [Int] conforms to Equatable because Int conforms to 
> Equatable
>   f(a2)    // error: [NotEquatable] does not conform to Equatable because 
> NotEquatable has no conformance to Equatable
> }
> Conditional conformances also have a run-time aspect, because a dynamic check 
> for a protocol conformance might rely on the evaluation of the extra 
> requirements needed to successfully use a conditional conformance. For 
> example:
> 
> protocol P {
>   func doSomething()
> }
> 
> struct S: P {
>   func doSomething() { print("S") }
> }
> 
> // Array conforms to P if it's element type conforms to P
> extension Array: P where Element: P {
>   func doSomething() {
>     for value in self {
>       value.doSomething()
>     }
>   }
> }
> 
> // Dynamically query and use conformance to P.
> func doSomethingIfP(_ value: Any) {
>   if let p = value as? P {
>     p.doSomething()
>   } else {
>     print("Not a P")
>   }
> }
> 
> doSomethingIfP([S(), S(), S()]) // prints "S" three times
> doSomethingIfP([1, 2, 3])       // prints "Not a P"
> The if-let in doSomethingIfP(_:) dynamically queries whether the type stored 
> in value conforms to the protocol P. In the case of an Array, that 
> conformance is conditional, which requires another dynamic lookup to 
> determine whether the element type conforms to P: in the first call to 
> doSomethingIfP(_:), the lookup finds the conformance of S to P. In the second 
> case, there is no conformance of Int to P, so the conditional conformance 
> cannot be used. The desire for this dynamic behavior motivates some of the 
> design decisions in this proposal.
> 
>  
> <https://github.com/DougGregor/swift-evolution/tree/conditional-conformances#detailed-design>Detailed
>  design
> 
> Most of the semantics of conditional conformances are obvious. However, there 
> are a number of issues (mostly involving multiple conformances) that require 
> more in-depth design.
> 
>  
> <https://github.com/DougGregor/swift-evolution/tree/conditional-conformances#disallow-overlapping-conformances>Disallow
>  overlapping conformances
> 
> With conditional conformances, it is possible to express that a given generic 
> type can conform to the same protocol in two different ways, depending on the 
> capabilities of its type arguments. For example:
> 
> struct SomeWrapper<Wrapped> {
>   let wrapped: Wrapped
> }
> 
> protocol HasIdentity {
>   static func ===(lhs: Self, rhs: Self) -> Bool
> }
> 
> extension SomeWrapper: Equatable where Wrapped: Equatable {
>   static func ==(lhs: SomeWrapper<Wrapped>, rhs: SomeWrapper<Wrapper>) -> 
> Bool {
>     return lhs.wrapped == rhs.wrapped
>   }
> }
> 
> extension SomeWrapper: Equatable where Wrapped: HasIdentity {
>   static func ==(lhs: SomeWrapper<Wrapped>, rhs: SomeWrapper<Wrapper>) -> 
> Bool {
>     return lhs.wrapped === rhs.wrapped
>   }
> }
> Note that, for an arbitrary type T, there are four potential answers to the 
> question of whether SomeWrapper<T> conforms to Equatable:
> 
> No, it does not conform because T is neither Equatable nor HasIdentity.
> Yes, it conforms via the first extension of SomeWrapper because T conforms to 
> Equatable.
> Yes, it conforms via the second extension of SomeWrapper because T conforms 
> to HasIdentity.
> Ambiguity, because T conforms to both Equatable and HasIdentity.
> It is due to the possibility of #4 occurring that we refer to the two 
> conditional conformances in the example as overlapping. There are designs 
> that would allow one to address the ambiguity, for example, by writing a 
> third conditional conformance that addresses #4:
> 
> // Possible tie-breaker conformance
> extension SomeWrapper: Equatable where Wrapped: Equatable & HasIdentity, {
>   static func ==(lhs: SomeWrapper<Wrapped>, rhs: SomeWrapper<Wrapper>) -> 
> Bool {
>     return lhs.wrapped == rhs.wrapped
>   }
> }
> The design is consistent, because this third conditional conformance is more 
> specialized the either of the first two conditional conformances, meaning 
> that its requirements are a strict superset of the requirements of those two 
> conditional conformances. However, there are a few downsides to such a system:
> 
> To address all possible ambiguities, one has to write a conditional 
> conformance for every plausible combination of overlapping requirements. To 
> statically resolve all ambiguities, one must also cover nonsensical 
> combinations where the two requirements are mutually exclusive (or invent a 
> way to state mutual-exclusivity).
> It is no longer possible to uniquely say what is required to make a generic 
> type conform to a protocol, because there might be several unrelated 
> possibilities. This makes reasoning about the whole system more complex, 
> because it admits divergent interfaces for the same generic type based on 
> their type arguments. At its extreme, this invites the kind of cleverness 
> we've seen in the C++ community with template metaprogramming, which is 
> something Swift has sought to avoid.
> All of the disambiguation machinery required at compile time (e.g., to 
> determine whether one conditional conformance is more specialized than 
> another to order them) also needs to implements in the run-time, as part of 
> the dynamic casting machinery. One must also address the possibility of 
> ambiguities occurring at run-time. This is both a sharp increase in the 
> complexity of the system and a potential run-time performance hazard.
> For these reasons, this proposal bans overlapping conformances entirely. 
> While the resulting system is less flexible than one that allowed overlapping 
> conformances, the gain in simplicity in this potentially-confusing area is 
> well worth the cost. Moreover, this ban follows with existing Swift rules 
> regarding multiple conformances, which prohibit the same type from conforming 
> to the same protocol in two different ways:
> 
> protocol P { }
> 
> struct S : P { }
> extension S : P { } // error: S already conforms to P
>  
> <https://github.com/DougGregor/swift-evolution/tree/conditional-conformances#implied-conditional-conformances>Implied
>  conditional conformances
> 
> Stating conformance to a protocol implicitly states conformances to any of 
> the protocols that it inherits. This is the case in Swift today, although 
> most developers likely don't realize the rules it follows. For example:
> 
> protocol P { }
> protocol Q : P { }
> protocol R : P { }
> 
> struct X1 { }
> struct X2 { }
> struct X3 { }
> 
> extension X1: Q { }  // implies conformance to P
> 
> extension X2: Q { }  // would imply conformance to P, but...
> extension X2: P { }  // explicitly-stated conformance to P "wins"
> 
> extension X3: Q { }  // implies conformance to P
> extension X3: R { }  // also implies conformance to P
>                      // one will "win"; which is unspecified
> With conditional conformances, the question of which extension "wins" the 
> implied conformance begins to matter, because the extensions might have 
> different constraints on them. For example:
> 
> struct X4<T> { }
> 
> extension X4: Q where T: Q { }  // implies conformance to P
> extension X4: R where T: R { }  // error: implies overlapping conformance to P
> Both of these constrained extensions imply a conformance to P, but the actual 
> P implied conformances to P are overlapping and, therefore, result in an 
> error.
> 
> However, in cases where there is a reasonable ordering between the two 
> constrained extensions (i.e., one is more specialized than the other), the 
> less specialized constrained extension should "win" the implied conformance. 
> Continuing the example from above:
> 
> protocol S: R { }
> 
> struct X5<T> { }
> 
> extension X5: R where T: R { }  // "wins" implied conformance to P, because
> extension X5: S where T: S { }  // the extension where "T: S" is more 
> specialized
>                                 // than the one where "T: R"
> Thus, the rule for placing implied conformances is to pick the least 
> specialized extension that implies the conformance. If there is more than one 
> such extension, then either:
> 
> All such extensions are not constrained extensions (i.e., they have no 
> requirements beyond what the type requires), in which case Swift can continue 
> to choose arbitrarily among the extensions, or
> All such extensions are constrained extensions, in which case the program is 
> ill-formed due to the ambiguity. The developer can explicitly specify 
> conformance to the protocol to disambiguate. 
>  
> <https://github.com/DougGregor/swift-evolution/tree/conditional-conformances#overloading-across-constrained-extensions>Overloading
>  across constrained extensions
> 
> One particularly important aspect of the placement rule for implied 
> conformances is that it affects which declarations are used to satisfy a 
> particular requirement. For example:
> 
> protocol P {
>   func f()
> }
> 
> protocol Q: P { }
> protocol R: Q { }
> 
> struct X1<T> { }
> 
> extension X1: Q where T: Q {           // note: implied conformance to P here
>   func f() {
>     // #1: basic implementation of 'f()'
>   }
> }
> 
> extension X1: R where T: R {
>   func f() {
>     // #2: superfast implementation of f() using some knowledge of 'R'
>   }
> }
> 
> struct X2: R {
>   func f() { }
> }
> 
> (X1<X2>() as P).f() // calls #1, which was used to satisfy the requirement 
> for 'f'
> X1<X2>().f()        // calls #2, which is preferred by overload resolution
> Effectively, when satisfying a protocol requirement, one can only choose from 
> members of the type that are guaranteed to available within the extension 
> with which the conformance is associated. In this case, the conformance to P 
> is placed on the first extension of X1, so the only f() that can be 
> considered is the f() within that extension: the f() in the second extension 
> won't necessarily always be available, because T may not conform to R. Hence, 
> the call that treats an X1<X2>as a P gets the first implementation of X1.f(). 
> When using the concrete type X1<X2>, where X2 conforms to R, both X.f() 
> implementations are visible... and the second is more specialized.
> 
> Technically, this issue is no different from surprises where (e.g.) a member 
> added to a concrete type in a different module won't affect an existing 
> protocol conformance. The existing ideas to mediate these problems---warning 
> for nearly-matching functions when they are declared in concrete types, for 
> example---will likely be sufficient to help surprised users. That said, this 
> proposal may increase the likelihood of such problems showing up.
> 
>  
> <https://github.com/DougGregor/swift-evolution/tree/conditional-conformances#source-compatibility>Source
>  compatibility
> 
> From the language perspective, conditional conformances are purely additive. 
> They introduce no new syntax, but instead provide semantics for existing 
> syntax---an extension that both declares a protocol conformance and has a 
> where clause---whose use currently results in a type checker failure. That 
> said, this is a feature that is expected to be widely adopted within the 
> Swift standard library, which may indirectly affect source compatibility.
> 
>  
> <https://github.com/DougGregor/swift-evolution/tree/conditional-conformances#effect-on-abi-stability>Effect
>  on ABI Stability
> 
> As noted above, there are a number of places where the standard library is 
> expected to adopt this feature, which fall into two classes:
> 
> Improve composability: the example in the introduction 
> <https://github.com/DougGregor/swift-evolution/blob/conditional-conformances/proposals/Introduction>
>  made Array conform to Equatable when its element type does; there are many 
> places in the Swift standard library that could benefit from this form of 
> conditional conformance, particularly so that collections and other types 
> that contain values (e.g., Optional) can compose better with generic 
> algorithms. Most of these changes won't be ABI- or source-breaking, because 
> they're additive.
> Eliminating repetition: the lazy wrappers described in the motivation 
> <https://github.com/DougGregor/swift-evolution/blob/conditional-conformances/proposals/motivation>
>  section could be collapsed into a single wrapper with several conditional 
> conformances. A similar refactoring could also be applied to the range 
> abstractions and slice types in the standard library, making the library 
> itself simpler and smaller. All of these changes are potentially 
> source-breaking and ABI-breaking, because they would remove types that could 
> be used in Swift 3 code. However, there are mitigations: generic typealiases 
> could provide source compatibility to Swift 3 clients, and the ABI-breaking 
> aspect is only relevant if conditional conformances and the standard library 
> changes they imply aren't part of Swift 4.
> Aside from the standard library, conditional conformances have an impact on 
> the Swift runtime, which will require specific support to handle dynamic 
> casting. If that runtime support is not available once ABI stability has been 
> declared, then introducing conditional conformances in a later language 
> version either means the feature cannot be deployed backward or that it would 
> provide only more limited, static behavior when used on older runtimes. 
> Hence, there is significant motivation for doing this feature as part of 
> Swift 4. Even if we waited to introduce conditional conformances, we would 
> want to include a hook in the runtime to allow them to be implemented later, 
> to avoid future backward-compatibility issues.
> 
>  
> <https://github.com/DougGregor/swift-evolution/tree/conditional-conformances#alternatives-considered>Alternatives
>  considered
> 
> The most common request related to conditional conformances is to allow a 
> (constrained) protocol extension to declare conformance to a protocol. For 
> example:
> 
> extension Collection: Equatable where Iterator.Element: Equatable {
>   static func ==(lhs: Self, rhs: Self) -> Bool {
>     // ...
>   }
> }
> This protocol extension will make any Collection of Equatable elements 
> Equatable, which is a powerful feature that could be put to good use. 
> Introducing conditional conformances for protocol extensions would exacerbate 
> the problem of overlapping conformances, because it would be unreasonable to 
> say that the existence of the above protocol extension means that no type 
> that conforms to Collection could declare its own conformance to Equatable, 
> conditional or otherwise.
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to