[swift-evolution] [Draft] Introducing StaticSelf, an Invariant Self

Matthew Johnson matthew at anandabits.com
Fri May 13 19:33:04 CDT 2016


> On May 13, 2016, at 3:12 PM, Joe Groff <jgroff at apple.com> wrote:
> 
>> 
>> On May 13, 2016, at 9:06 AM, Matthew Johnson <matthew at anandabits.com> wrote:
>> 
>> 
>>> On May 13, 2016, at 10:55 AM, Joe Groff <jgroff at apple.com> wrote:
>>> 
>>> 
>>>> On May 13, 2016, at 8:18 AM, Matthew Johnson <matthew at anandabits.com> wrote:
>>>> 
>>>> When I write a class Base with non-final methods that return instances of Base I can choose whether to state the return type as Self (covariant) or Base (invariant, under this proposal StaticSelf would also be an alternative way to state this).  If I choose to specify Base as the return type derived classes *may* override the method but are not required to.  Further, if they *do* override the method they are allowed to choose whether their implementation returns Base or Derived.
>>> 
>>> `StaticSelf` requirements by themselves don't even save you from covariance. If Base conforms to a protocol (with Self == Base), Derived inherits that conformance and also conforms (with Self == Derived). If `StaticSelf` always refers to the conforming type, then it must also be bindable to Base and Derived, so a base class must still use a covariant-returning method to satisfy the `StaticSelf` requirement.
>> 
>> We are specifying that `StaticSelf` refers to the type that explicitly declares conformance.  If a class inherits conformance it refers to the base class which explicitly declared the conformance it is inheriting.
> 
> That makes `StaticSelf` tricky to use in generic code. This would be invalid:
> 
> protocol Makable {
> 	static func make(value: Int) -> StaticSelf
> }
> 
> func makeWithZero<T: Fooable>(x: Int) -> T {
> 	return T.make(value: 0) // ERROR: T.StaticSelf may be a supertype of T so isn't convertible to T
> }

I agree it’s a bit tricky.  But that’s better than not possible at all.  You just need a typealias and a same type constraint to make this work as expected / desired:

protocol Makable {
	typealias RootMakable = StaticSelf
	static func make(value: Int) -> StaticSelf
}

func makeWithZero<T: Makable where T == T.RootMakable>(x: Int) -> T {
	return T.make(value: 0) // works now 
}

Now that we have a typealias we can refer to the binding of StaticSelf and constrain it as necessary for whatever purpose we have in mind.  In some cases that will be a same type constraint so that our code works properly with class clusters.  I don’t have concrete examples of other use cases but can imagine use cases constraining the typealias to a protocol, for example.

If we had control over inheritance of conformance at the point of conformance we probably wouldn’t be talking about StaticSelf.  But we don’t and this is a problem that has caused enough people trouble that it is worth solving.  StaticSelf does that in a general way that is also as a shorthand in types themselves and has consistent semantics in both use cases.  

IIRC the design of point-of-conformance control over inheritance of conformance is pretty thorny.  I wouldn’t mind seeing that feature eventually but don’t have any confidence that it will come soon.  

> 
> `StaticSelf` in this model is effectively an associated type of the protocol, with a `Self: StaticSelf` constraint (if that were supported).

If you add that the associated type is automatically bound with the initial conformance (and cannot be modified by subclass conformances) then yes, you can look at it this way.



> 
> -Joe

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160513/3276330a/attachment.html>


More information about the swift-evolution mailing list