> 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