> On Dec 28, 2015, at 10:31 AM, Matthew Johnson <[email protected]> wrote:
> 
>> 
>> On Dec 28, 2015, at 12:02 PM, Joe Groff <[email protected] 
>> <mailto:[email protected]>> wrote:
>> 
>>> 
>>> On Dec 28, 2015, at 8:49 AM, Matthew Johnson via swift-evolution 
>>> <[email protected] <mailto:[email protected]>> wrote:
>>> 
>>> I have brought up the idea of a non-covarying Self a few times.  
>>> 
>>> I was surprised to realize that Self is actually non-covarying when used 
>>> for parameters in protocol declarations!
>>> 
>>> Here is an example demonstrating this:
>>> 
>>> protocol P {
>>>   func foo(s: Self)
>>> }
>>> protocol Q {
>>>   func bar() -> Self
>>> }
>>> 
>>> class C: P {
>>>   // this works!  Self as an argument type in the protocol declaration does 
>>> not covary
>>>   func foo(c: C) {}
>>> }
>>> 
>>> class D: C {}
>>> 
>>> extension C: Q {
>>>   // method ‘bar()’ in non-final class ‘C’ must return ‘Self’ to conform to 
>>> protocol ‘Q'
>>>   func bar() -> C { return self } 
>>> }
>>> 
>>> 
>>> It doesn’t make sense to allow a co-varying Self for parameters so I can 
>>> understand how the current state might have arisen.  At the same time, 
>>> using Self to mean two different things is inconsistent, confusing and it 
>>> doesn’t allow us to specify a non-covarying Self as a return type in 
>>> protocol requirements.  
>>> 
>>> As I have pointed out before, the ability to specify a non-covarying Self 
>>> as a return type would make it possible to design a protocol that can be 
>>> retroactively conformed to by non-final classes (such as those in Apple’s 
>>> frameworks).
>>> 
>>> I think it would be a very good idea to introduce a non-covarying Self 
>>> which would specify the type that adds conformance to the protocol and 
>>> require this Self to be used in places where covariance is not possible, 
>>> such as parameter types.  It would also be allowed elsewhere, such as 
>>> return types, making it easier to conform non-final classes when covariance 
>>> is not required by the protocol.
>>> 
>>> One possible name is `ConformingSelf`.  One thing I like about this name is 
>>> that it makes it very clear that it is the type that introduces protocol 
>>> conformance.
>>> 
>>> I’m interested in hearing thoughts on this.
>> 
>> I proposed this a while back — see "controlling protocol conformance 
>> inheritance" from a while back. The current rules preserve inheritability of 
>> the protocol conformance in all cases. `Self` in argument positions maps to 
>> the root class of the conformance, since a method taking `Base` can also 
>> take any `Derived` class and thereby satisfy the conformance for `Derived`. 
>> In return positions, the derived type must be produced. I think there's 
>> value in controlling this behavior, but the control belongs on the 
>> conformer's side, not the protocol's. For some class hierarchies, the 
>> subclasses are intended to be API themselves in order to extend behavior. 
>> Every UIView subclass is interesting independently, for example, and ought 
>> to satisfy `NSCoding` and other requirements independently. In other class 
>> hierarchies, the base class is intended to be the common API, and subclasses 
>> are just implementation details. NSString, NSURL, etc. exemplify this—for 
>> most purposes the common `NSCoding` implementation is sufficient for all 
>> NSStrings. Likewise, you probably want NSURL to conform to 
>> `StringLiteralConvertible` but don't particularly care what subclass you get 
>> out of the deal. I had proposed the idea of modifying a class's conformance 
>> declaration to allow it control whether the conformance is inherited:
>> 
>> extension NSString: static NSCoding { } // NSCoding conformance statically 
>> applies to only NSString, Self == NSString
>> extension UIView: required NSCoding { } // NSCoding conformance is required 
>> of all subclasses, Self <= UIView
>> 
> 
> If I understand correctly, what you’re saying is that you don’t think there 
> is a reason why a protocol would want to specifically require invariance.  I 
> would have to think about this some more but you may well be right.  The 
> conforming-side solution would definitely work in every use case I know of.
> 
> I figured out how Self protocol requirements are actually treated 
> consistently from a particular perspective.  Self is effectively treated as a 
> covariant requirement, but becomes an invariant requirement when used in 
> positions where covariance is not possible (parameters and return type for 
> structs and final classes).  This makes sense and is consistent, if not 
> totally obvious.  Is this reasonably accurate?

Yeah. It might help to think of `Self` within the protocol as referring to the 
full range of types [BaseClass, Self] that are required to conform to the 
protocol. Covariance and contravariance push the interval in opposite 
directions; in argument position, you need to cover the range by specifying its 
upper bound `BaseClass`, but in return position, you need to specify the lower 
bound `Self`.

> 
> From that perspective, it seems Self should also behave this way when used as 
> a return type in method signatures:
> 
> protocol P {
>     func bar() -> Self
> }
> final class C: P {
>     // this doesn’t work, but maybe it should because C is final and can’t 
> covary:
>     func bar() -> Self { return C() }
> 
>     // this works:
>     // func bar() -> C { return C() }
> }
> 
> struct S: P {
>     // this doesn’t work, but maybe it should because S is a struct and can’t 
> covary:
>     func bar() -> Self { return S() }
> 
>     // this works:
>     // func bar() -> S { return S() }
> }

Yeah, it would be nice if we treated `Self` consistently. For an invariant type 
like a final class or struct, it would always be synonymous with the declared 
type.

> 
> This thread was prompted by work on a proposal for protocol forwarding which 
> requires careful consideration of Self requirements.  
> 
> One of the things I have considered is whether it would be possible and 
> desirable to wrap and forward the entire interface of a type rather than just 
> specific protocols.  As it turns out, this is not possible in a robust manner 
> without using Self in the signature where the type should be promoted to the 
> forwarding type (i.e. just because a method on Double takes a Double 
> parameter you don’t necessarily want to promote the type of that parameter to 
> Kilograms when wrapping Double).

Interesting idea. I worry that that's a very subtle difference between 
`TypeName` and `Self`. I think you also really would need a separate 
`InvariantSelf` specifier for classes that are both forwardable and inheritable.

-Joe

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

Reply via email to