> On Sep 29, 2016, at 3:05 PM, Russ Bishop <[email protected]> wrote:
> 
> 
>> On Sep 29, 2016, at 11:12 AM, Douglas Gregor <[email protected] 
>> <mailto:[email protected]>> wrote:
>> 
>>> 
>>> On Sep 28, 2016, at 9:48 PM, Russ Bishop <[email protected] 
>>> <mailto:[email protected]>> wrote:
>>> 
>>> 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.
> 
> Yeah your description is already sounding like a lot of work :)
> 
> 
>> 
>>> 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.
> 
> Just to clarify in my mind, the problem here is that Swift would need runtime 
> machinery to look at Wrapped and select the conformance to Equatable based on 
> whether Wrapped: Equatable or Wrapped: HasIdentity. Is that right?


Correct. And diagnose / disambiguate if there are multiple conformances that 
match.

> Otherwise with the proposal as written Swift would need to check Wrapped to 
> validate the constraints but once it does there is only one implementation of 
> the conformance to pick from. 

Right.

> I believe you about the type checker, I’m just naively assuming inserting a 
> table to select the correct conformance isn’t a big cost because you would 
> canonicalize the constraints and being disjoint for any type T there would 
> only ever be one matching entry.

The table computation would have to be a runtime thing, because we don’t know 
all of the conformances until then, but yes—it’s doable.

>> 
>>> 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”.
> 
> Yes, I was also imagining protocols so long as there is no protocol they 
> share in common except the empty protocol.

The compiler won’t know that a given type can’t conform to two specific 
protocols, though, unless they have some kind of direct conflict (like my 
example above of Wrapped.Element being equated to two different concrete types) 
or we have some mechanism in the language to state that two protocols are 
mutually exclusive.

>>> 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.
> 
> The problem is importing a random library can immediately introduce breakage 
> when it is a compile error, or worse if both reference a shared library you 
> also import… unless we’re saying extensions of types outside your module are 
> only visible in the declaring module which is pretty restrictive e.g. some UI 
> toolkit extending UIKit/AppKit classes with conveniences, or extending Array 
> to say it CanLayout if elements are views where calling a.layout() tells all 
> the views in the array to layout. In that example neither the views nor Array 
> would be declared in the module doing the extending.
> 
> Now let’s say I want to use the swift-protobuf library and I also use 
> GenericSocialMediaService’ SDK that also incorporates swift-protobuf. I’m 
> just imagining what happens when we both try to define extensions. It would 
> be nice if they could declare Array: ProtobufMessage where Element: 
> GSMSEntityProtocol but I was able to provide Array: ProtobufMessage where 
> Element: MyOwnProtocol. 

Yes, I know.

> That said the restrictions can always be relaxed later. I’d rather have this 
> feature without overlapping conformances than not have it.

Right. If we find a model for it that works.

> 
> 
>> 
>>>> 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”.
> 
> What I’m saying is that if T: Q then the entire implementation of P must come 
> from the first extension. If T: R then the entire implementation of P must 
> come from the second extension.

This fails when T: Q&R, though, because you don’t know which implementation of 
P to choose—the one based on T: Q or the one based on T: R? That’s the reason 
for completing banning the overlap—it avoids the possibility of ambiguity (at 
compile time or at runtime).

> If T: P then neither extension applies because there are overlapping 
> extensions. Basically if there is any overlap then the compiler only 
> considers explicit extensions that match without ambiguity, otherwise the 
> extension is ignored. 
> 
> func takes<Value: P>(value: Value) { }
> 
> struct CQ: Q { }
> struct CR: R { }
> struct CP: P { }
> 
> takes(X4<CQ>()) //fine, uses first extension
> takes(X4<CR>()) //fine, uses second extension 
> takes(X4<CP>()) //error: ambiguous conformance        

In the model you describe, this last one isn’t “ambiguous conformance”, it’s 
“no conformance” because neither of the (potential) conformances of X4 to P has 
its requirements satisfied. The case you didn’t enumerate is:

struct CQR: Q, R { }
takes(X4<CQR>()) // error: ambiguous conformance


> This relates to the disjoint discussion above:
> 
> extension X4: P where T: P { } //error: ambiguous conformance
> 
> Even though there is technically a conformance to P available we just ignore 
> it.

I don’t understand your “error: ambiguous conformance” annotation here. This 
extension of X4 is less specialized than either of the other extensions, yet 
provides a suitable P conformance, and (if it were present in your example 
immediately above) would allow “takes(X4<CP>())” to succeed. Indeed, this is 
the fix for the example in SE-0143 because it eliminates the overlap by 
providing a common, shared conformance to P.

>  Seems like this would be knowable statically by looking at the extensions 
> and the protocol relationships in the constraints?


You can see that either of the first two extensions is more specialized than 
this third extension, if you can see them all. Dynamic work comes in when 
multiple modules with extensions are linked together.

>>> 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.
> 
> That’s what I was trying to address by saying no mix-n-match. If you’re going 
> to wonder into overlapping territory you must supply two entirely separate 
> implementations of P, one explicit and one inside the Q extension. (I fully 
> admit that might not help the implementation complexity - I don’t know enough 
> about the type checker to answer that).

If we were to allow overlap, then yes, maybe it should have to be explicit. My 
complaints/concerns about overlapping conformances still apply ;)

        - Doug

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

Reply via email to