Self behaves inconsistently in protocol method signatures


(Matthew Johnson) #1

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.

Matthew


(Joe Groff) #2

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

-Joe

···

On Dec 28, 2015, at 8:49 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> 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.


(Robert Widmann) #3

My understanding of Self is that it is a special generic parameter resolved by the type system to the type of the implementing structure. That resolution must be invariant because the implementing structure (here, non-final classes) can choose to yank the protocol's invariants out from under you when it is subclassed. Sure, retroactively, you can make things conform, but you also can't completely guarantee type safety with any kind of variance in Self in all cases.

On the other hand, using the protocol itself in either position says that you only wish to restrict yourself to the protocol itself, not some specific implementation. You are necessarily specifying an upper bound (here C) on the amount of "information" you can get out of the type, so it is possible to introduce variance because you will never violate the protocol's invariants by returning a subtype with a legal conformance.

Self doesn't mean two different things, your protocol declarations do!

~Robert Widmann

2015/12/28 11:49、Matthew Johnson via swift-evolution <swift-evolution@swift.org> のメッセージ:

···

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.

Matthew
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #4

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?

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() }
}

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).

Matthew

···

On Dec 28, 2015, at 12:02 PM, Joe Groff <jgroff@apple.com> wrote:

On Dec 28, 2015, at 8:49 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> 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


(Joe Groff) #5

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

···

On Dec 28, 2015, at 10:31 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Dec 28, 2015, at 12:02 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Dec 28, 2015, at 8:49 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Matthew Johnson) #6

My understanding of Self is that it is a special generic parameter resolved by the type system to the type of the implementing structure. That resolution must be invariant because the implementing structure (here, non-final classes) can choose to yank the protocol's invariants out from under you when it is subclassed. Sure, retroactively, you can make things conform, but you also can't completely guarantee type safety with any kind of variance in Self in all cases.

On the other hand, using the protocol itself in either position says that you only wish to restrict yourself to the protocol itself, not some specific implementation. You are necessarily specifying an upper bound (here C) on the amount of "information" you can get out of the type, so it is possible to introduce variance because you will never violate the protocol's invariants by returning a subtype with a legal conformance.

Self doesn't mean two different things, your protocol declarations do!

My mind must be a little bit foggy this morning. This works:

extension C: Q {
    func bar() -> Self { return self }
}

What doesn’t work, regardless of whether C is final or not, is this:

extension C: Q {
    // Cannot convert return expression of type ‘C’ to return type ‘Self'
    func bar() -> Self { return C() }
}

In order for classes to meet a protocol requirement with Self in the return position you must specify Self (rather than the conforming type) as the return type for the method. Self in the return position of a method is treated as covariant.

In order for classes to meet a protocol requirement with Self in parameter position you must specify the type of the conforming class (you cannot specify Self in an argument position). Obviously the type of the conforming class is invariant.

This is the sense in which Self in protocol declarations is inconsistent. The requirements on conforming types are different - invariance for Self parameters and covariance for Self return types.

IMO it would be much more clear if this distinction was explicit rather than implicit based on the location of Self. It would also be extremely useful in some cases to be able to specify an invariant `ConformingSelf` return type.

···

On Dec 28, 2015, at 11:19 AM, Developer <devteam.codafi@gmail.com> wrote:

~Robert Widmann

2015/12/28 11:49、Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> のメッセージ:

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.

Matthew
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Robert Widmann) #7

That doesn't look like a variance issue to me, that's about the same "information" invariant I talked about before. The former works because self resolves to an invariant type, the type of the implementing structure, which satisfies the requirement Self introduces. The latter does not because Self indicates a level of specificity C cannot guarantee. Self is magic, but it is also implemented as a generic parameter. So think of it this way:

protocol Q {
  func bar<T>() -> T { return Q() }
}

You wouldn't expect that to compile, would you?

~Robert Widmann

2015/12/28 12:49、Matthew Johnson <matthew@anandabits.com> のメッセージ:

···

On Dec 28, 2015, at 11:19 AM, Developer <devteam.codafi@gmail.com> wrote:

My understanding of Self is that it is a special generic parameter resolved by the type system to the type of the implementing structure. That resolution must be invariant because the implementing structure (here, non-final classes) can choose to yank the protocol's invariants out from under you when it is subclassed. Sure, retroactively, you can make things conform, but you also can't completely guarantee type safety with any kind of variance in Self in all cases.

On the other hand, using the protocol itself in either position says that you only wish to restrict yourself to the protocol itself, not some specific implementation. You are necessarily specifying an upper bound (here C) on the amount of "information" you can get out of the type, so it is possible to introduce variance because you will never violate the protocol's invariants by returning a subtype with a legal conformance.

Self doesn't mean two different things, your protocol declarations do!

My mind must be a little bit foggy this morning. This works:

extension C: Q {
    func bar() -> Self { return self }
}

What doesn’t work, regardless of whether C is final or not, is this:

extension C: Q {
    // Cannot convert return expression of type ‘C’ to return type ‘Self'
    func bar() -> Self { return C() }
}

In order for classes to meet a protocol requirement with Self in the return position you must specify Self (rather than the conforming type) as the return type for the method. Self in the return position of a method is treated as covariant.

In order for classes to meet a protocol requirement with Self in parameter position you must specify the type of the conforming class (you cannot specify Self in an argument position). Obviously the type of the conforming class is invariant.

This is the sense in which Self in protocol declarations is inconsistent. The requirements on conforming types are different - invariance for Self parameters and covariance for Self return types.

IMO it would be much more clear if this distinction was explicit rather than implicit based on the location of Self. It would also be extremely useful in some cases to be able to specify an invariant `ConformingSelf` return type.

~Robert Widmann

2015/12/28 11:49、Matthew Johnson via swift-evolution <swift-evolution@swift.org> のメッセージ:

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.

Matthew
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Matthew Johnson) #8

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`.

That makes sense.

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.

Yeah, I think it is an idea that seems good at first but has a lot of problems. Even if the language supported the necessary signatures and the standard library got them right I imagine many people would be confused and get them wrong in practice.

With protocol forwarding you can still forward at least most of the interface, even retroactively. Any method that can be declared in a protocol and conformed to by the delegate would be possible to forward. With more control over how conformance is inherited it might be possible to forward without limitation (I haven’t considered it deeply enough to find potential limits, but they may still exist). The only cost is the need to declare a protocol with the signatures you need to forward and declare conformance by the delegate. In some cases this might feel like boilerplate, but it would only need to happen once and the delegate could then be wrapped N times so it isn’t too bad.

If protocol forwarding can cover all, or nearly all, necessary cases I’m not sure the complexity of direct interface forwarding would be worth the added complexity. My instinct is that it probably would not be.

Matthew

···

On Dec 28, 2015, at 12:43 PM, Joe Groff <jgroff@apple.com> wrote:

On Dec 28, 2015, at 10:31 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

On Dec 28, 2015, at 12:02 PM, Joe Groff <jgroff@apple.com <mailto:jgroff@apple.com>> wrote:

On Dec 28, 2015, at 8:49 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Matthew Johnson) #9

That doesn't look like a variance issue to me, that's about the same "information" invariant I talked about before. The former works because self resolves to an invariant type, the type of the implementing structure, which satisfies the requirement Self introduces. The latter does not because Self indicates a level of specificity C cannot guarantee. Self is magic, but it is also implemented as a generic parameter. So think of it this way:

protocol Q {
  func bar<T>() -> T { return Q() }
}

You wouldn't expect that to compile, would you?

It actually does work for structs and for final classes because Self becomes invariant when used in a return type position for them.

protocol Q {
    func bar() -> Self
}

final class C: Q {
    func bar() -> C { return C() }
}

···

On Dec 28, 2015, at 12:04 PM, Developer <devteam.codafi@gmail.com> wrote:

~Robert Widmann

2015/12/28 12:49、Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> のメッセージ:

On Dec 28, 2015, at 11:19 AM, Developer <devteam.codafi@gmail.com <mailto:devteam.codafi@gmail.com>> wrote:

My understanding of Self is that it is a special generic parameter resolved by the type system to the type of the implementing structure. That resolution must be invariant because the implementing structure (here, non-final classes) can choose to yank the protocol's invariants out from under you when it is subclassed. Sure, retroactively, you can make things conform, but you also can't completely guarantee type safety with any kind of variance in Self in all cases.

On the other hand, using the protocol itself in either position says that you only wish to restrict yourself to the protocol itself, not some specific implementation. You are necessarily specifying an upper bound (here C) on the amount of "information" you can get out of the type, so it is possible to introduce variance because you will never violate the protocol's invariants by returning a subtype with a legal conformance.

Self doesn't mean two different things, your protocol declarations do!

My mind must be a little bit foggy this morning. This works:

extension C: Q {
    func bar() -> Self { return self }
}

What doesn’t work, regardless of whether C is final or not, is this:

extension C: Q {
    // Cannot convert return expression of type ‘C’ to return type ‘Self'
    func bar() -> Self { return C() }
}

In order for classes to meet a protocol requirement with Self in the return position you must specify Self (rather than the conforming type) as the return type for the method. Self in the return position of a method is treated as covariant.

In order for classes to meet a protocol requirement with Self in parameter position you must specify the type of the conforming class (you cannot specify Self in an argument position). Obviously the type of the conforming class is invariant.

This is the sense in which Self in protocol declarations is inconsistent. The requirements on conforming types are different - invariance for Self parameters and covariance for Self return types.

IMO it would be much more clear if this distinction was explicit rather than implicit based on the location of Self. It would also be extremely useful in some cases to be able to specify an invariant `ConformingSelf` return type.

~Robert Widmann

2015/12/28 11:49、Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> のメッセージ:

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.

Matthew
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution