> On Aug 16, 2016, at 8:52 PM, Charles Srstka <[email protected]> wrote:
>
>> On Aug 16, 2016, at 8:51 PM, Slava Pestov <[email protected]
>> <mailto:[email protected]>> wrote:
>>
>> 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.
>
> Argh, that’s particularly frustrating since in something like ‘func foo<T :
> P>(t: T)’ or ‘func foo<S : Sequence>(s: S) where S.IteratorElement: P’,
> you’re only ever getting instances anyway since the parameter is in the
> input, so calling initializers or static functions isn’t something you can
> even do (unless you call .dynamicType, at which point you *do* have a
> concrete type at runtime thanks to the dynamic check).
Well, if you have ‘func foo<T : P>(t: T)’, then you can write
T.someStaticMember() to call static members — it’s true you also have an
instance ’t’, but you can also work directly with the type. But I suspect this
is not what you meant, because:
>
> I wish there were a way to have partial conformance in cases like these. Like
> how this causes what’s probably Swift’s most confusing compiler error
> (certainly one of its most asked about):
>
> protocol P: Equatable {
> static func ==(l: Self, r: Self) -> Bool
>
> func foo()
> }
>
> struct S: P {
> static func ==(l: S, r: S) -> Bool {
> return true
> }
>
> func foo() {
> print("foo")
> }
> }
>
> let s = S()
> let p = s as P // error: Protocol ‘P’ can only be used as a generic
> constraint because it has Self or associated type requirements
Yep :) So the property of ‘can be used as an existential type’ is actually a
bit different from ‘protocol conforms to itself’. The rules here are:
- Self must not appear in contravariant position
- Protocol has no associated types
Note that static members and initializers are OK, and you can call them via
‘p.dynamicType.foo()’ where p : P.
>
> It would make using protocols so much less teeth-grinding if the compiler
> *did* allow you to type the variable as P, but then would just throw an error
> if you tried to call one of the “problem” methods (in this case, using the
> ‘==' operator would be an error, but calling ‘foo’ would be fine). If this
> were possible, the conformance for a variable typed P would just not pick up
> “illegal” things like initializers, and would also leave out conformance for
> things like 'makeIt()' above which return the generic parameter in the
> output, rather than the input, necessitating a concrete type. I’m probably
> dreaming, I know.
In the type checker, this more precise, per-member check is already
implemented, interestingly enough. It comes up with protocol extensions.
Imagine you have a protocol ‘P’ that can be used as an existential, but an
extension of P adds a problematic member:
protocol P {
func f() -> Int
}
extension P {
func ff(other: Self) -> Int { return f() + s.f()) }
}
Here, you don’t want to entirely ban the type ‘P’, because the extension might
come from another module, and it shouldn’t just break everyone’s code. So the
solution is that you can use ‘P’ as an existential, but if you try to reference
‘p.ff’ where p : P, you get a diagnostic, because that particular member is
unavailable.
In fact, I think the separate restriction that rules out usage of the overall
type when one of the protocol’s requirements is problematic, is mostly
artificial, in that it could just be disabled and you’d be able to pass around
‘Equatable’ values, etc, because the lower layers don’t care (I think).
I do remember it was explained to me at one point that this is how it was in
the early days of Swift, but it made code completion and diagnostics confusing,
because with some protocols (like Sequence) most members became inaccessible.
A better approach is to implement more general existential types which expose
ways of working with their associated types, rather than just banning certain
members from being used altogether. This is described in Doug's ‘completing
generics’ document, and again, it is quite a large project :)
>
> Actually, what I wish is that Swift had an equivalent of the 'id <P>’ type in
> Objective-C. That notation always stood for an instance of something that
> conformed to P, rather than "maybe P itself, and maybe something that
> conforms to it”. If we could do that, we could just pass sequences of 'id
> <P>’ (in whatever syntax we gave it in Swift) to a sequence where Element: P,
> and it’d work fine regardless of anything that prevented P from conforming to
> P.
In fact I think some of the proposals call for Any<P> as the syntax for the
most general existential of type ‘P’, with other syntax when associated types
are bound. I must admit I haven’t followed the discussions around generalized
existentials very closely though.
So it sounds like your original :== operator idea is really about implementing
self-conforming protocols, as well as generalized existentials. These are quite
difficult projects, but I hope we’ll tackle them one day. Patches are welcome
:-)
>
> Charles
>
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution