> On Sep 28, 2016, at 9:48 PM, Russ Bishop <[email protected]> wrote:
> 
> 
>> On Sep 26, 2016, at 5:18 PM, Douglas Gregor via swift-evolution 
>> <[email protected] <mailto:[email protected]>> wrote:
>> 
>> Conditional conformances
>> 
>>  
>> <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:
>> 
>> …
>> 
>> 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 these reasons, this proposal bans overlapping conformances entirely. 
> 
> What other designs were considered and rejected? It seems like some kind of 
> escape hatch would be preferred if you happen to get into this situation, 
> though you make some really good points about the pitfalls.

I don’t have a fully-baked alternative proposal—it would probably have to 
involve some kind of preference rule for picking the “best” set of 
(consistent!) conformances to satisfy a particular request, introduce a 
disambiguation syntax for cases where that preference rule does the wrong 
thing, and some way of teaching the dynamic-casting machinery to do the same 
thing.

> Just to clarify when you say “bans” do you mean if Wrapped: Equatable & 
> HasIdentity then SomeWrapper is not Equatable, or do you mean you get a 
> compile error because there are two constrained conformances SomeWrapper: 
> Equatable?

You get a compile error if there are two conformances of SomeWrapper to 
Equatable; it doesn’t actually matter whether they are conditional, but people 
are far more likely to expect to be able to having overlapping conditional 
conformances.

> What would be the problem with allowing multiple conformances to Equatable so 
> long as the constraints are disjoint

>From the language perspective, “disjoint” would have to mean that there are 
>requirements that actively conflict, e.g., one extension has “Wrapped.Element 
>== Int” and the other has “Wrapped.Element == String”.

There are implementation issues here deep in the type checker, e.g., because if 
a given type T can potential conform to a protocol P in multiple ways, it 
introduces a disjunction in the constraint solver that can push the constraint 
solver to be Even More Exponential.

For me, there’s also the usability issue, and that’s the key argument: the 
*human* has to reason about these things, too, and it is a whole lot simpler if 
“does T conform to P?” can only be answered in one way. I don’t think the use 
cases for having overlapping conformances justify such a drastic increase in 
complexity across the feature.

> or the concrete type only adopts one of the available protocols?

Unless you assume that you have a fully-determined, closed system where you 
know about every potential conformance of a concrete type, this isn’t a 
question that can be answered at compile time.

> 
>>  
>> <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
> On X2 you’re declaring a redundant conformance to P but any protocol 
> extensions will prefer Q and the compiler won’t let you redefine any members 
> so you’ll have an incomplete conformance. Any explicit conformances (on the 
> type or in extensions) are preferred over the defaults from the protocol 
> extension, but that’s not new. I must be missing something, how would this be 
> visible in Swift 3?


X2 states that it conforms to Q. That implies that it also conforms to P, 
because of course you can’t have a Q that isn’t a P.

Because X2 explicitly states that it conforms to P, the implication of P via Q 
doesn’t actually realize an actual conformance to P, because that conformance 
would be redundant. The rule is, basically, that an explicitly-stated 
conformance to a protocol suppresses any implied conformances to that protocol.

> On X3, multiple implementations in protocol extensions are errors today and 
> the resolution is to provide an explicit implementation on X3.

Not true! The code above is well-formed in Swift 3. Each extension of X3 above 
implies a conformance to P; it simply does not matter which extension actually 
realizes the conformance to P so long as only one of them does.

You could actually see which one it chooses by putting these into separate 
files—in the same module—and looking at the generated SIL.

>> 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.
>> 
> If the related P conformance were inherited from conformance to Q or R then 
> the rules would (IMHO) make more sense. Wouldn’t the extra rule you need 
> simply be that either Q or R must provide a complete conformance to P (no 
> mix-n-match)? 

A conformance to P introduced by the first extension would not be usable by the 
second extension, because the conformance to P introduced by the first 
extension might depend on details of “T: Q”… and the second extension can’t 
assume that “T: Q”.

> 
> If T implements Q & P why not just ignore T: P which means the X4: R 
> extension is no longer relevant. 

Implementing Q implies implementing P.

> 
> It seems like the tricky case is T: P and the same question applies - why not 
> just ignore the extensions (X4<T> in that scenario doesn’t implement Q, R, or 
> P). 
> 
> 
> Not allowing ambiguity seems like it solves the “which one” problem and 
> requiring an extension to provide the entire implementation (no mix-n-match) 
> cuts down on the cleverness problem.

For reference, there was an early point in Swift 3.0 where I had added an error 
when it wasn’t clear which extension should realize a conformance to a protocol 
that was implied by multiple extensions (i.e., exactly the case of X3 that we 
discussed above), thinking that it would clear up the ambiguity and make code 
more obvious. Developers *hated* this error, because it doesn’t matter to the 
semantics of the program nor to their mental models, so I took it out.

>> 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. 
> What is the rationale for picking the least specialized extension? That’s not 
> what I would naively expect to happen. If T: R & S then I would expect the 
> more specialized S:R implementation to be preferred, and the explicit R 
> implementation to kick in when T: R. 

We have to pick the least-specialized extension, because it’s the only one that 
works. If you pick a more-specialized extension to realize the implied 
conformance to P, the less-specialized extension doesn’t have a conformance to 
P that it can rely on.

I’ll see about clarifying the proposal here, because the choice of (unique) 
least-specialized isn’t arbitrary: it’s the only answer that provides a correct 
result without introducing overlapping conformances.

        - Doug


_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to