> On Aug 16, 2016, at 6:51 PM, Slava Pestov <[email protected]> wrote:
> 
>> 
>> On Aug 16, 2016, at 6:40 PM, Charles Srstka <[email protected] 
>> <mailto:[email protected]>> wrote:
>> 
>>> On Aug 16, 2016, at 8:13 PM, Slava Pestov <[email protected] 
>>> <mailto:[email protected]>> wrote:
>>> 
>>> -1 — this adds a new syntax with little gain, and potentially a lot of 
>>> additional complexity.
>>> 
>>>> On Aug 16, 2016, at 2:49 PM, Charles Srstka via swift-evolution 
>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>> 
>>>> Unfortunately, when this has come up on the list in the past, it has been 
>>>> mentioned that there are some cases where an existential of a protocol 
>>>> does not conform to the protocol itself, so it is impossible to make : 
>>>> always match items that are typed to the protocol.
>>> 
>>> Indeed, the best solution IMHO would be to implement self-conforming 
>>> protocols, so that what you’re describing can be expressed without any 
>>> additional syntax.
>>> 
>>> The condition for a protocol to be able to conform to itself is the 
>>> following:
>>> 
>>> - it must not have any associated type requirements, or contravariant Self 
>>> in requirement signatures; eg, this rules out the following:
>>> 
>>>  protocol P { func foo(s: Self) }
>>> 
>>> - it must not have any static method or initializer requirements
>>> 
>>> With these conditions met, it would be possible to allow a generic 
>>> parameter ’T : P’ to bind to a concrete type ’P’.
>> 
>> Well if it can be done, then that’s great. The reason I thought of a new 
>> modifier is because the last time I suggested extending : to include the 
>> protocol itself, the reaction was quite negative, suggesting that the amount 
>> of work necessary to do that would be outside the bounds of what could be 
>> considered reasonable.
> 
> The amount of work is certainly not trivial, but this feature request comes 
> up often enough that I think we should try to tackle it at some point.
> 
>> I am a little concerned about the second requirement. Protocols that include 
>> static methods and initializers work perfectly well inside arrays, and 
>> restricting them from generic collections will further discourage use of the 
>> latter in favor of the former.
> 
> Here is why we must have that requirement. Consider the following code:
> 
> protocol P {
>   init()
> }
> 
> struct A : P {
>   init() {}
> }
> 
> struct B : P {
>   init() {}
> }
> 
> func makeIt<T : P>() -> T {
>   return T()
> }
> 
> I can use this function as follows:
> 
> let a: A = makeIt() // Creates a new ‘A'
> let a: B = makeIt() // Creates a new ‘B’
> 
> Now suppose we allow P to self-conform. Then the following becomes valid:
> 
> let p: P = makeIt()
> 
> What exactly would makeIt() do in this case? There’s no concrete type passed 
> in, or any way of getting one, so there’s nothing to construct. The same 
> issue would come up with static methods here.

Yeah, so I should add one way around this is to factor your protocol into two — 
Q can be a self-conforming base protocol, and P can refine Q with additional 
requirements such as initializers. This means that forming a type Array<P> and 
passing it around is totally fine; you just can’t pass an Array<P> to a 
function with type <T : P> Array<T> -> …, because P cannot bind to <T : P>. 
You’d be able to pass an Array<P> to a functio nwith type <T : Q> Array<T> -> … 
though — the substitution T := P would be permitted in this case, since there 
are no static requirements visible on ’T’.

Slava

> 
> 
>> 
>>> Note that the type checker work required for this is not very difficult. 
>>> Indeed, we already allow @objc protocols that don’t have static 
>>> requirements to self-conform. The real issue is the runtime representation 
>>> gets tricky, if you want to allow a generic parameter to contain both a 
>>> concrete type conforming to P, and an existential of P. Basically a generic 
>>> parameter is passed as three values behind the scenes, the actual value, 
>>> type metadata for the concrete type, and a witness table for the 
>>> conformance. To allow the parameter to be bound to an existential type we 
>>> would need to pass in a special witness table that unpacks the existential 
>>> and calls the witness table contained in the existential.
>>> 
>>> It’s even worse if the protocol that self-conforms is a class-bound 
>>> protocol. A generic parameter conforming to a class-bound protocol is 
>>> passed as a reference counted pointer and witness table. Unfortunately, a 
>>> class-bound existential is *not* a reference counted pointer — it has the 
>>> witness table ‘inside’ the value.
>>> 
>>> Probably my explanation isn’t great, but really what’s bothering you here 
>>> isn’t a language limitation, it’s an implementation limitation — once we 
>>> figure out how to represent protocol existentials efficiently in a way 
>>> allowing them to self-conform, we should be able to address these use-cases 
>>> without new syntax.
>> 
>> What I’ve long wondered is why we don’t have this problem with arrays.
>> 
>> protocol MyProto {
>>     func baz()
>>     
>>     // Includes static and initializer requirements
>>     static func qux()
>>     init()
>> }
>> 
>> struct MyStruct: MyProto {
>>     func baz() {
>>         print("baz")
>>     }
>>     
>>     static func qux() {
>>         print("qux")
>>     }
>>     
>>     init() {
>>         print("init")
>>     }
>> }
>> 
>> func foo(bar: [MyProto]) {
>>     for eachMyProto in bar {
>>         eachMyProto.baz()
>>     }
>> }
>> 
>> let x = [MyStruct()]
>> let y = x as [MyProto]
>> 
>> foo(bar: x)
>> foo(bar: y)
>> 
>> This compiles and runs fine. Why is that?
> 
> Recall that an Array is just a (very complex) generic struct in Swift:
> 
> struct Array<Element> {
>   …
> }
> 
> The key here is that there are *no generic requirements* placed on the 
> parameter ‘Element’.
> 
> So both Array<MyStruct> and Array<MyProto> are perfectly reasonable types, 
> because ‘Element’ can be bound to any type, since there’s nothing you can 
> *do* with an ‘Element’, except for what you can do with all values, which is 
> assign it into a location, load it from a location, or cast it to something.
> 
> So binding Element to a protocol type is fine — there’s no witness table of 
> operations passed behind the scenes, because there are no requirements. The 
> representational issue I detailed in my previous e-mail only comes up if 
> additional requirements are placed on the generic parameter.
> 
> Hopefully this clarifies things!
> 
> Slava
> 
>> 
>> Charles

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

Reply via email to