> 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