[Pitch] Generalized supertype constraints

One of the most frequent frustrations I encounter when writing generic code in Swift is the requirement that supertype constraints be concrete. When I mentioned this on Twitter (https://twitter.com/anandabits/status/929958479598534656\) Doug Gregor mentioned that this feature is smaller and mostly straightforward to design and implement (https://twitter.com/dgregor79/status/929975472779288576\).

I currently have a PR open to add the high-level description of this feature found below to the generics manifesto (https://github.com/apple/swift/pull/13012\):

Currently, supertype constraints may only be specified using a concrete class or protocol type. This prevents us from abstracting over the supertype.

protocol P {
  associatedtype Base
  associatedtype Derived: Base
}

In the above example `Base` may be any type. `Derived` may be the same as `Base` or may be _any_ subtype of `Base`. All subtype relationships supported by Swift should be supported in this context including, but not limited to, classes and subclasses, existentials and conforming concrete types or refining existentials, `T?` and `T`, `((Base) -> Void)` and `((Derived) -> Void)`, etc.

Generalized supertype constraints would be accepted in all syntactic locations where generic constraints are accepted.

I would like to see generalized supertype constraints make it into Swift 5 if possible. I am not an implementer so I will not be able to bring a proposal forward alone but am interested in collaborating with anyone interested in working on implementation.

I am also interested in hearing general feedback on this feature from the community at large. Have you also found this limitation frustrating? In what contexts? Does anyone have reservations about introducing this capability? If so, what are they?

Matthew

10 Likes

In general this is more then welcome, so +1 for me.

However I have one question:

Could this allow support, or at least be a first step towards Swift allowing the following behaviour?

extension MyProtocol where Self : SomeClass {
	static func getSubtypes<T>(ofType _: T.Type = T.self) -> [T] where T : Self { ... }
}

I would like to be able to upgrade `Self` to a class constraint, which then will allow me to only accept subtypes from T at compile time.

···

Am 25. November 2017 um 00:03:23, Matthew Johnson via swift-evolution (swift-evolution@swift.org) schrieb:

One of the most frequent frustrations I encounter when writing generic code in Swift is the requirement that supertype constraints be concrete. When I mentioned this on Twitter (https://twitter.com/anandabits/status/929958479598534656\) Doug Gregor mentioned that this feature is smaller and mostly straightforward to design and implement (https://twitter.com/dgregor79/status/929975472779288576\).

I currently have a PR open to add the high-level description of this feature found below to the generics manifesto (https://github.com/apple/swift/pull/13012\):

Currently, supertype constraints may only be specified using a concrete class or protocol type. This prevents us from abstracting over the supertype.

protocol P {
  associatedtype Base
  associatedtype Derived: Base
}

In the above example `Base` may be any type. `Derived` may be the same as `Base` or may be _any_ subtype of `Base`. All subtype relationships supported by Swift should be supported in this context including, but not limited to, classes and subclasses, existentials and conforming concrete types or refining existentials, `T?` and `T`, `((Base) -> Void)` and `((Derived) -> Void)`, etc.

Generalized supertype constraints would be accepted in all syntactic locations where generic constraints are accepted.

I would like to see generalized supertype constraints make it into Swift 5 if possible. I am not an implementer so I will not be able to bring a proposal forward alone but am interested in collaborating with anyone interested in working on implementation.

I am also interested in hearing general feedback on this feature from the community at large. Have you also found this limitation frustrating? In what contexts? Does anyone have reservations about introducing this capability? If so, what are they?

Matthew

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

I think this is essential for creating good, general-purpose abstractions with Swift.

For example, currently I have a protocol which models a CRUD database. There is some ORM behaviour which translates the DB information in to model objects, but of course, each database might have different requirements. Currently ORMs use class hierarchies to give you that behaviour - e.g. CoreData has NSManagedObject, Realm has its own “Object” class, etc. This requires that I write separate protocols for each base-type, like this:

protocol RealmObjectStore {
    func create<S>(_ objects: S) where S: Sequence, S.Element: RealmSwift.Object
    func read<T>(_ command: FetchCommand<T>) -> [T] where T: RealmSwift.Object
    // …etc
}

Ideally, my my protocol would look something like this:

protocol ObjectStore {
    associatedtype Storable // might be a concrete type; might be a protocol.

    func create<S>(_ objects: S) where S: Sequence, S.Element: Self.Storable
    func read<T>(_ command: FetchCommand<T>) -> [T] where T: Self.Storable
    // …etc
}

That would allow me to abstract over CoreData and Realm using a single protocol, as far as they are both able to act like CRUD databases:

class CoreDataStore: ObjectStore {
    typealias Storable = NSManagedObject
   // …etc
}

class RealmDataStore: ObjectStore {
    typealias Storable = RealmObject
   // …etc
}

Okay, so technically I could do that today by requiring that Storable is a subclass of NSObject. But doing this would also us to move beyond class hierarchies - what does an ObjectStore really need to store and retrieve its data? Well, that depends on the store. Maybe some will require all of the dynamism and reflection of Objective-C, and so will continue to require subclassing, but maybe for some it will be enough to have Codable conformance and a primary key. You could satisfy that with a value-type:

protocol ValueDBStorable: Codable {
    var primaryKey: KeyPath<Self, String> { get }
}

class ValueDBStore: ObjectStore {
    typealias Storable = ValueDBStorable
    // …etc
}

So I think it’s a very powerful feature and would love to use it. However, I do think it would underscore the need for nested protocols.

- Karl

···

On 25. Nov 2017, at 00:03, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

One of the most frequent frustrations I encounter when writing generic code in Swift is the requirement that supertype constraints be concrete. When I mentioned this on Twitter (https://twitter.com/anandabits/status/929958479598534656\) Doug Gregor mentioned that this feature is smaller and mostly straightforward to design and implement (https://twitter.com/dgregor79/status/929975472779288576\).

I currently have a PR open to add the high-level description of this feature found below to the generics manifesto (https://github.com/apple/swift/pull/13012\):

Currently, supertype constraints may only be specified using a concrete class or protocol type. This prevents us from abstracting over the supertype.

protocol P {
  associatedtype Base
  associatedtype Derived: Base
}

In the above example `Base` may be any type. `Derived` may be the same as `Base` or may be _any_ subtype of `Base`. All subtype relationships supported by Swift should be supported in this context including, but not limited to, classes and subclasses, existentials and conforming concrete types or refining existentials, `T?` and `T`, `((Base) -> Void)` and `((Derived) -> Void)`, etc.

Generalized supertype constraints would be accepted in all syntactic locations where generic constraints are accepted.

I would like to see generalized supertype constraints make it into Swift 5 if possible. I am not an implementer so I will not be able to bring a proposal forward alone but am interested in collaborating with anyone interested in working on implementation.

I am also interested in hearing general feedback on this feature from the community at large. Have you also found this limitation frustrating? In what contexts? Does anyone have reservations about introducing this capability? If so, what are they?

Matthew

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

This thread received very light, but positive feedback. I would really like to see this feature added and am willing to draft and official proposal but am not able to implement it. If anyone is interested in collaborating just let me know.

···

On Nov 24, 2017, at 5:03 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

One of the most frequent frustrations I encounter when writing generic code in Swift is the requirement that supertype constraints be concrete. When I mentioned this on Twitter (https://twitter.com/anandabits/status/929958479598534656\) Doug Gregor mentioned that this feature is smaller and mostly straightforward to design and implement (https://twitter.com/dgregor79/status/929975472779288576\).

I currently have a PR open to add the high-level description of this feature found below to the generics manifesto (https://github.com/apple/swift/pull/13012\):

Currently, supertype constraints may only be specified using a concrete class or protocol type. This prevents us from abstracting over the supertype.

protocol P {
  associatedtype Base
  associatedtype Derived: Base
}

In the above example `Base` may be any type. `Derived` may be the same as `Base` or may be _any_ subtype of `Base`. All subtype relationships supported by Swift should be supported in this context including, but not limited to, classes and subclasses, existentials and conforming concrete types or refining existentials, `T?` and `T`, `((Base) -> Void)` and `((Derived) -> Void)`, etc.

Generalized supertype constraints would be accepted in all syntactic locations where generic constraints are accepted.

I would like to see generalized supertype constraints make it into Swift 5 if possible. I am not an implementer so I will not be able to bring a proposal forward alone but am interested in collaborating with anyone interested in working on implementation.

I am also interested in hearing general feedback on this feature from the community at large. Have you also found this limitation frustrating? In what contexts? Does anyone have reservations about introducing this capability? If so, what are they?

Matthew

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

I would also love to have generic associated types in the language, I have a lot of uses for them and, IIUC, supertype constraint would enable me to express the following:

protocol Service {}

protocol WikiService: Service {} // methods not shown for conciseness
class DefaultWikiService: WikiService {}
class DemoWikiService: WikiService {}

class DataServiceManager {

    private var registry = [String: Service]()

    func register<S>(_ service: Service, ofType type: S.Type) where S: Service {
        let key = "\(Swift.type(of: type))"
        registry[key] = service
    }

    func service<S>(ofType type: S.Type) -> S where S: Service {
        let key = "\(Swift.type(of: type))"

        // It is a programmer error to expect a value for a not yet registered type
        guard let service = registry[key] as? S else {
            fatalError("Service of type \(type) cannot be found. Please register a service for that type before accessing it.")
        }
        return service
    }

}

let manager = DataServiceManager()
if isDemoMode {
    manager.register(DemoWikiService(), ofType: WikiService.self) // Currently: error: in argument type 'WikiService.Protocol', 'WikiService' does not conform to expected type 'Service'
} else {
    manager.register(DefaultWikiService(), ofType: WikiService.self) // Currently: error: in argument type 'WikiService.Protocol', 'WikiService' does not conform to expected type 'Service'
}

If that's right, I'm also +1 on this :)

- Dennis

···

On Nov 25, 2017, at 12:13 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

In general this is more then welcome, so +1 for me.

However I have one question:

Could this allow support, or at least be a first step towards Swift allowing the following behaviour?

extension MyProtocol where Self : SomeClass {
	static func getSubtypes<T>(ofType _: T.Type = T.self) -> [T] where T : Self { ... }
}

I would like to be able to upgrade `Self` to a class constraint, which then will allow me to only accept subtypes from T at compile time.

Am 25. November 2017 um 00:03:23, Matthew Johnson via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

One of the most frequent frustrations I encounter when writing generic code in Swift is the requirement that supertype constraints be concrete. When I mentioned this on Twitter (https://twitter.com/anandabits/status/929958479598534656\) Doug Gregor mentioned that this feature is smaller and mostly straightforward to design and implement (https://twitter.com/dgregor79/status/929975472779288576\).

I currently have a PR open to add the high-level description of this feature found below to the generics manifesto (https://github.com/apple/swift/pull/13012\):

Currently, supertype constraints may only be specified using a concrete class or protocol type. This prevents us from abstracting over the supertype.

protocol P {
  associatedtype Base
  associatedtype Derived: Base
}

In the above example `Base` may be any type. `Derived` may be the same as `Base` or may be _any_ subtype of `Base`. All subtype relationships supported by Swift should be supported in this context including, but not limited to, classes and subclasses, existentials and conforming concrete types or refining existentials, `T?` and `T`, `((Base) -> Void)` and `((Derived) -> Void)`, etc.

Generalized supertype constraints would be accepted in all syntactic locations where generic constraints are accepted.

I would like to see generalized supertype constraints make it into Swift 5 if possible. I am not an implementer so I will not be able to bring a proposal forward alone but am interested in collaborating with anyone interested in working on implementation.

I am also interested in hearing general feedback on this feature from the community at large. Have you also found this limitation frustrating? In what contexts? Does anyone have reservations about introducing this capability? If so, what are they?

Matthew

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

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

Hello Matthew, I have more more question about the generalized supertype constraints. Does the generalization also covers inout? I found a really annoying case with inout.

protocol P {}

func foo(_ p: inout P) {
    print(p)
}

struct F : P {}

var f = F()

foo(&f) // won't compile until we explicitly write `var f: P = F()`
Shouldn’t we allow inout to have subtypes as well, like inout F : inout P?

This is actually one of the pain points with the generic version of the print function. We cannot pass an arbitrary TextOutputStream to it without sacrificing the type. I doubt the generic print function is justified, because TextOuputStream does not have associated types nor a Self constraint. Furthermore it forces you to create a custom type-erased wrapper that can hold an arbitrary TextOutputSteram.

If this is part of a totally different topic, I’ll move it in it’s own thread.

···

Am 2. Dezember 2017 um 19:03:24, Matthew Johnson via swift-evolution (swift-evolution@swift.org) schrieb:

This thread received very light, but positive feedback. I would really like to see this feature added and am willing to draft and official proposal but am not able to implement it. If anyone is interested in collaborating just let me know.

On Nov 24, 2017, at 5:03 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

One of the most frequent frustrations I encounter when writing generic code in Swift is the requirement that supertype constraints be concrete. When I mentioned this on Twitter (https://twitter.com/anandabits/status/929958479598534656\) Doug Gregor mentioned that this feature is smaller and mostly straightforward to design and implement (https://twitter.com/dgregor79/status/929975472779288576\).

I currently have a PR open to add the high-level description of this feature found below to the generics manifesto (https://github.com/apple/swift/pull/13012\):

Currently, supertype constraints may only be specified using a concrete class or protocol type. This prevents us from abstracting over the supertype.

protocol P {
  associatedtype Base
  associatedtype Derived: Base
}

In the above example `Base` may be any type. `Derived` may be the same as `Base` or may be _any_ subtype of `Base`. All subtype relationships supported by Swift should be supported in this context including, but not limited to, classes and subclasses, existentials and conforming concrete types or refining existentials, `T?` and `T`, `((Base) -> Void)` and `((Derived) -> Void)`, etc.

Generalized supertype constraints would be accepted in all syntactic locations where generic constraints are accepted.

I would like to see generalized supertype constraints make it into Swift 5 if possible. I am not an implementer so I will not be able to bring a proposal forward alone but am interested in collaborating with anyone interested in working on implementation.

I am also interested in hearing general feedback on this feature from the community at large. Have you also found this limitation frustrating? In what contexts? Does anyone have reservations about introducing this capability? If so, what are they?

Matthew

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

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

Well no, this proposal won’t allow your example. The problem in your example actually has different roots - *Metatypes*. As by today, meta types are somehow broken, especially in generic / associated type context. Furthermore there is currently no way to express that you may want a subtype for an existential metatype instead of the static metatype.

For more informations about meta types and who some of us would like them to work, read our proposal here:

Then your example could be expressed as:

func register<S>(_ service: Service, ofType type: Type<S>) where AnyType<S> : AnyType<Service> {}

// or if I’m not mistaken we can make use if implicit existential like this then
func register<S>(_ service: Service, ofType type: Type<S>) where S : Service {}

I don't want to dive any deeper about the metatype pain points, because I don’t want to prevent the success of the pitched idea with an off-topic.

Am 25. November 2017 um 16:34:58, Dennis Weissmann (dennis@dennisweissmann.me) schrieb:

I would also love to have generic associated types in the language, I have a lot of uses for them and, IIUC, supertype constraint would enable me to express the following:

protocol Service {}

protocol WikiService: Service {} // methods not shown for conciseness
class DefaultWikiService: WikiService {}
class DemoWikiService: WikiService {}

class DataServiceManager {

private var registry = \[String: Service\]\(\)

func register&lt;S&gt;\(\_ service: Service, ofType type: S\.Type\) where S: Service \{
    let key = &quot;\\\(Swift\.type\(of: type\)\)&quot;
    registry\[key\] = service
\}

func service&lt;S&gt;\(ofType type: S\.Type\) \-&gt; S where S: Service \{
    let key = &quot;\\\(Swift\.type\(of: type\)\)&quot;

    // It is a programmer error to expect a value for a not yet registered type
    guard let service = registry\[key\] as? S else \{
        fatalError\(&quot;Service of type \\\(type\) cannot be found\. Please register a service for that type before accessing it\.&quot;\)
    \}
    return service
\}

}

let manager = DataServiceManager()
if isDemoMode {
manager.register(DemoWikiService(), ofType: WikiService.self) // Currently: error: in argument type 'WikiService.Protocol', 'WikiService' does not conform to expected type 'Service'
} else {
manager.register(DefaultWikiService(), ofType: WikiService.self) // Currently: error: in argument type 'WikiService.Protocol', 'WikiService' does not conform to expected type 'Service'
}

If that's right, I'm also +1 on this :)

- Dennis

···

On Nov 25, 2017, at 12:13 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

In general this is more then welcome, so +1 for me.

However I have one question:

Could this allow support, or at least be a first step towards Swift allowing the following behaviour?

extension MyProtocol where Self : SomeClass {
static func getSubtypes<T>(ofType _: T.Type = T.self) -> [T] where T : Self { ... }
}

I would like to be able to upgrade `Self` to a class constraint, which then will allow me to only accept subtypes from T at compile time.

Am 25. November 2017 um 00:03:23, Matthew Johnson via swift-evolution (swift-evolution@swift.org) schrieb:

One of the most frequent frustrations I encounter when writing generic code in Swift is the requirement that supertype constraints be concrete. When I mentioned this on Twitter (https://twitter.com/anandabits/status/929958479598534656\) Doug Gregor mentioned that this feature is smaller and mostly straightforward to design and implement (https://twitter.com/dgregor79/status/929975472779288576\).

I currently have a PR open to add the high-level description of this feature found below to the generics manifesto (https://github.com/apple/swift/pull/13012\):

Currently, supertype constraints may only be specified using a concrete class or protocol type. This prevents us from abstracting over the supertype.

protocol P {
  associatedtype Base
  associatedtype Derived: Base
}

In the above example `Base` may be any type. `Derived` may be the same as `Base` or may be _any_ subtype of `Base`. All subtype relationships supported by Swift should be supported in this context including, but not limited to, classes and subclasses, existentials and conforming concrete types or refining existentials, `T?` and `T`, `((Base) -> Void)` and `((Derived) -> Void)`, etc.

Generalized supertype constraints would be accepted in all syntactic locations where generic constraints are accepted.

I would like to see generalized supertype constraints make it into Swift 5 if possible. I am not an implementer so I will not be able to bring a proposal forward alone but am interested in collaborating with anyone interested in working on implementation.

I am also interested in hearing general feedback on this feature from the community at large. Have you also found this limitation frustrating? In what contexts? Does anyone have reservations about introducing this capability? If so, what are they?

Matthew

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

No, inout should *not* be covariant, since foo can assign any subtype of P to its parameter, not just F:

protocol P {}
struct F: P {}
struct G: P {}

func foo(_ p: inout P) {
    p = G()
}

var f = F()
foo(&f) // assigns a value of type G to a variable of type F

From Jonathan

···

On Dec 10, 2017, at 12:51 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello Matthew, I have more more question about the generalized supertype constraints. Does the generalization also covers inout? I found a really annoying case with inout.

protocol P {}

func foo(_ p: inout P) {
    print(p)
}

struct F : P {}

var f = F()

foo(&f) // won't compile until we explicitly write `var f: P = F()`
Shouldn’t we allow inout to have subtypes as well, like inout F : inout P?

This is actually one of the pain points with the generic version of the print function. We cannot pass an arbitrary TextOutputStream to it without sacrificing the type. I doubt the generic print function is justified, because TextOuputStream does not have associated types nor a Self constraint. Furthermore it forces you to create a custom type-erased wrapper that can hold an arbitrary TextOutputSteram.

If this is part of a totally different topic, I’ll move it in it’s own thread.

Am 2. Dezember 2017 um 19:03:24, Matthew Johnson via swift-evolution (swift-evolution@swift.org) schrieb:

This thread received very light, but positive feedback. I would really like to see this feature added and am willing to draft and official proposal but am not able to implement it. If anyone is interested in collaborating just let me know.

On Nov 24, 2017, at 5:03 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

One of the most frequent frustrations I encounter when writing generic code in Swift is the requirement that supertype constraints be concrete. When I mentioned this on Twitter (https://twitter.com/anandabits/status/929958479598534656\) Doug Gregor mentioned that this feature is smaller and mostly straightforward to design and implement (https://twitter.com/dgregor79/status/929975472779288576\).

I currently have a PR open to add the high-level description of this feature found below to the generics manifesto (https://github.com/apple/swift/pull/13012\):

Currently, supertype constraints may only be specified using a concrete class or protocol type. This prevents us from abstracting over the supertype.

protocol P {
  associatedtype Base
  associatedtype Derived: Base
}

In the above example `Base` may be any type. `Derived` may be the same as `Base` or may be _any_ subtype of `Base`. All subtype relationships supported by Swift should be supported in this context including, but not limited to, classes and subclasses, existentials and conforming concrete types or refining existentials, `T?` and `T`, `((Base) -> Void)` and `((Derived) -> Void)`, etc.

Generalized supertype constraints would be accepted in all syntactic locations where generic constraints are accepted.

I would like to see generalized supertype constraints make it into Swift 5 if possible. I am not an implementer so I will not be able to bring a proposal forward alone but am interested in collaborating with anyone interested in working on implementation.

I am also interested in hearing general feedback on this feature from the community at large. Have you also found this limitation frustrating? In what contexts? Does anyone have reservations about introducing this capability? If so, what are they?

Matthew

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

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

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

I see, that makes totally sense. I thought about `inout` only in terms of mutation not reassignment from within the scope itself.

Am 10. Dezember 2017 um 17:56:56, Jonathan Keller (jkeller234@icloud.com) schrieb:

No, inout should *not* be covariant, since foo can assign any subtype of P to its parameter, not just F:

protocol P {}
struct F: P {}
struct G: P {}

func foo(_ p: inout P) {
p = G()
}

var f = F()
foo(&f) // assigns a value of type G to a variable of type F

From Jonathan

···

On Dec 10, 2017, at 12:51 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello Matthew, I have more more question about the generalized supertype constraints. Does the generalization also covers inout? I found a really annoying case with inout.

protocol P {}

func foo(_ p: inout P) {
    print(p)
}

struct F : P {}

var f = F()

foo(&f) // won't compile until we explicitly write `var f: P = F()`
Shouldn’t we allow inout to have subtypes as well, like inout F : inout P?

This is actually one of the pain points with the generic version of the print function. We cannot pass an arbitrary TextOutputStream to it without sacrificing the type. I doubt the generic print function is justified, because TextOuputStream does not have associated types nor a Self constraint. Furthermore it forces you to create a custom type-erased wrapper that can hold an arbitrary TextOutputSteram.

If this is part of a totally different topic, I’ll move it in it’s own thread.

Am 2. Dezember 2017 um 19:03:24, Matthew Johnson via swift-evolution (swift-evolution@swift.org) schrieb:

This thread received very light, but positive feedback. I would really like to see this feature added and am willing to draft and official proposal but am not able to implement it. If anyone is interested in collaborating just let me know.

On Nov 24, 2017, at 5:03 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

One of the most frequent frustrations I encounter when writing generic code in Swift is the requirement that supertype constraints be concrete. When I mentioned this on Twitter (https://twitter.com/anandabits/status/929958479598534656\) Doug Gregor mentioned that this feature is smaller and mostly straightforward to design and implement (https://twitter.com/dgregor79/status/929975472779288576\).

I currently have a PR open to add the high-level description of this feature found below to the generics manifesto (https://github.com/apple/swift/pull/13012\):

Currently, supertype constraints may only be specified using a concrete class or protocol type. This prevents us from abstracting over the supertype.

protocol P {
  associatedtype Base
  associatedtype Derived: Base
}

In the above example `Base` may be any type. `Derived` may be the same as `Base` or may be _any_ subtype of `Base`. All subtype relationships supported by Swift should be supported in this context including, but not limited to, classes and subclasses, existentials and conforming concrete types or refining existentials, `T?` and `T`, `((Base) -> Void)` and `((Derived) -> Void)`, etc.

Generalized supertype constraints would be accepted in all syntactic locations where generic constraints are accepted.

I would like to see generalized supertype constraints make it into Swift 5 if possible. I am not an implementer so I will not be able to bring a proposal forward alone but am interested in collaborating with anyone interested in working on implementation.

I am also interested in hearing general feedback on this feature from the community at large. Have you also found this limitation frustrating? In what contexts? Does anyone have reservations about introducing this capability? If so, what are they?

Matthew

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

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

You’re right, I misunderstood that paragraph (maybe I read what I wanted to read :D). Thank you very much for the clarification and I’ll take a closer look at your proposal tomorrow!

- Dennis

···

Sent from my iPhone

On 25. Nov 2017, at 10:37 PM, Adrian Zubarev <adrian.zubarev@devandartist.com> wrote:

Well no, this proposal won’t allow your example. The problem in your example actually has different roots - *Metatypes*. As by today, meta types are somehow broken, especially in generic / associated type context. Furthermore there is currently no way to express that you may want a subtype for an existential metatype instead of the static metatype.

For more informations about meta types and who some of us would like them to work, read our proposal here:
https://github.com/DevAndArtist/swift-evolution/blob/refactor_existential_metatypes/proposals/0126-refactor-metatypes.md

Then your example could be expressed as:

func register<S>(_ service: Service, ofType type: Type<S>) where AnyType<S> : AnyType<Service> {}

// or if I’m not mistaken we can make use if implicit existential like this then
func register<S>(_ service: Service, ofType type: Type<S>) where S : Service {}

I don't want to dive any deeper about the metatype pain points, because I don’t want to prevent the success of the pitched idea with an off-topic.

Am 25. November 2017 um 16:34:58, Dennis Weissmann (dennis@dennisweissmann.me) schrieb:

I would also love to have generic associated types in the language, I have a lot of uses for them and, IIUC, supertype constraint would enable me to express the following:

protocol Service {}

protocol WikiService: Service {} // methods not shown for conciseness
class DefaultWikiService: WikiService {}
class DemoWikiService: WikiService {}

class DataServiceManager {

    private var registry = [String: Service]()

    func register<S>(_ service: Service, ofType type: S.Type) where S: Service {
        let key = "\(Swift.type(of: type))"
        registry[key] = service
    }

    func service<S>(ofType type: S.Type) -> S where S: Service {
        let key = "\(Swift.type(of: type))"

        // It is a programmer error to expect a value for a not yet registered type
        guard let service = registry[key] as? S else {
            fatalError("Service of type \(type) cannot be found. Please register a service for that type before accessing it.")
        }
        return service
    }

}

let manager = DataServiceManager()
if isDemoMode {
    manager.register(DemoWikiService(), ofType: WikiService.self) // Currently: error: in argument type 'WikiService.Protocol', 'WikiService' does not conform to expected type 'Service'
} else {
    manager.register(DefaultWikiService(), ofType: WikiService.self) // Currently: error: in argument type 'WikiService.Protocol', 'WikiService' does not conform to expected type 'Service'
}

If that's right, I'm also +1 on this :)

- Dennis

On Nov 25, 2017, at 12:13 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

In general this is more then welcome, so +1 for me.

However I have one question:

Could this allow support, or at least be a first step towards Swift allowing the following behaviour?

extension MyProtocol where Self : SomeClass {
static func getSubtypes<T>(ofType _: T.Type = T.self) -> [T] where T : Self { ... }
}

I would like to be able to upgrade `Self` to a class constraint, which then will allow me to only accept subtypes from T at compile time.

Am 25. November 2017 um 00:03:23, Matthew Johnson via swift-evolution (swift-evolution@swift.org) schrieb:

One of the most frequent frustrations I encounter when writing generic code in Swift is the requirement that supertype constraints be concrete. When I mentioned this on Twitter (https://twitter.com/anandabits/status/929958479598534656\) Doug Gregor mentioned that this feature is smaller and mostly straightforward to design and implement (https://twitter.com/dgregor79/status/929975472779288576\).

I currently have a PR open to add the high-level description of this feature found below to the generics manifesto (https://github.com/apple/swift/pull/13012\):

Currently, supertype constraints may only be specified using a concrete class or protocol type. This prevents us from abstracting over the supertype.

protocol P {
  associatedtype Base
  associatedtype Derived: Base
}

In the above example `Base` may be any type. `Derived` may be the same as `Base` or may be _any_ subtype of `Base`. All subtype relationships supported by Swift should be supported in this context including, but not limited to, classes and subclasses, existentials and conforming concrete types or refining existentials, `T?` and `T`, `((Base) -> Void)` and `((Derived) -> Void)`, etc.

Generalized supertype constraints would be accepted in all syntactic locations where generic constraints are accepted.

I would like to see generalized supertype constraints make it into Swift 5 if possible. I am not an implementer so I will not be able to bring a proposal forward alone but am interested in collaborating with anyone interested in working on implementation.

I am also interested in hearing general feedback on this feature from the community at large. Have you also found this limitation frustrating? In what contexts? Does anyone have reservations about introducing this capability? If so, what are they?

Matthew

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

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

Mutation is more or less modeled as reassignment in Swift, there is no difference between a variable that can be mutated and one that can be reassigned (right?). There are however differences between inout's real behaviour and what some might expect; some might expect that it is modified at the same time (and as many times) as if the function was inlined.

struct F {
    var a : Int {
        didSet {
            print("set \(a)")
        }
    }
}

func setTimes(a: inout Int, times: Int) {
    for _ in 1...times {
        a += 1
    }
}

var foo = F(a: 0)

// prints set 1 … set 10
for _ in 1...10 {
    foo.a += 1
}

setTimes(a: &foo.a, times: 10) // only prints set 20

However, your generalization is already possible, just change the function head to:

func foo<E:P>(_ p: inout E)

which, if we add a member to the protocol and something meaningful for the function to do becomes something like this:

protocol P { var a : Int {get set} }

func foo<E:P>(_ p: inout E) {
    p.a += 1
}

struct F : P { var a : Int }

var f = F(a: 0)

foo(&f)
print(f)

Was there some other problem you were trying to solve that can't be solved by doing this?

/Magnus

···

11 Dec. 2017 03:49 Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

I see, that makes totally sense. I thought about `inout` only in terms of mutation not reassignment from within the scope itself.

Am 10. Dezember 2017 um 17:56:56, Jonathan Keller (jkeller234@icloud.com) schrieb:

No, inout should *not* be covariant, since foo can assign any subtype of P to its parameter, not just F:

protocol P {}
struct F: P {}
struct G: P {}

func foo(_ p: inout P) {
    p = G()
}

var f = F()
foo(&f) // assigns a value of type G to a variable of type F

From Jonathan

On Dec 10, 2017, at 12:51 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello Matthew, I have more more question about the generalized supertype constraints. Does the generalization also covers inout? I found a really annoying case with inout.

protocol P {}

func foo(_ p: inout P) {
    print(p)
}

struct F : P {}

var f = F()

foo(&f) // won't compile until we explicitly write `var f: P = F()`

Shouldn’t we allow inout to have subtypes as well, like inout F : inout P?

This is actually one of the pain points with the generic version of the print function. We cannot pass an arbitrary TextOutputStream to it without sacrificing the type. I doubt the generic print function is justified, because TextOuputStream does not have associated types nor a Self constraint. Furthermore it forces you to create a custom type-erased wrapper that can hold an arbitrary TextOutputSteram.

If this is part of a totally different topic, I’ll move it in it’s own thread.

Am 2. Dezember 2017 um 19:03:24, Matthew Johnson via swift-evolution (swift-evolution@swift.org) schrieb:

This thread received very light, but positive feedback. I would really like to see this feature added and am willing to draft and official proposal but am not able to implement it. If anyone is interested in collaborating just let me know.

On Nov 24, 2017, at 5:03 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

One of the most frequent frustrations I encounter when writing generic code in Swift is the requirement that supertype constraints be concrete. When I mentioned this on Twitter (https://twitter.com/anandabits/status/929958479598534656\) Doug Gregor mentioned that this feature is smaller and mostly straightforward to design and implement (https://twitter.com/dgregor79/status/929975472779288576\).

I currently have a PR open to add the high-level description of this feature found below to the generics manifesto (https://github.com/apple/swift/pull/13012\):

Currently, supertype constraints may only be specified using a concrete class or protocol type. This prevents us from abstracting over the supertype.

protocol P {
  associatedtype Base
  associatedtype Derived: Base
}

In the above example `Base` may be any type. `Derived` may be the same as `Base` or may be _any_ subtype of `Base`. All subtype relationships supported by Swift should be supported in this context including, but not limited to, classes and subclasses, existentials and conforming concrete types or refining existentials, `T?` and `T`, `((Base) -> Void)` and `((Derived) -> Void)`, etc.

Generalized supertype constraints would be accepted in all syntactic locations where generic constraints are accepted.

I would like to see generalized supertype constraints make it into Swift 5 if possible. I am not an implementer so I will not be able to bring a proposal forward alone but am interested in collaborating with anyone interested in working on implementation.

I am also interested in hearing general feedback on this feature from the community at large. Have you also found this limitation frustrating? In what contexts? Does anyone have reservations about introducing this capability? If so, what are they?

Matthew

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

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

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

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

1 Like

I ran into a limitation in Swift today, and found this pitch after searching around a bit for others who have encountered the same limitations. Would this pitch allow something like the following?

func evaluateAndCast<Root, Value, Other>(
    _ keyPath: KeyPath<Root, Value>,
    from root: Root
) -> Other where Value: Other {
    root[keyPath: keyPath] as Other
}

Compiler: Type 'Value' constrained to non-protocol, non-class type 'Other'

However, the following is possible:

protocol SomeProtocol {}
class SomeClass {}

func evaluateAndCast<Root, Value>(
    _ keyPath: KeyPath<Root, Value>,
    from root: Root
) -> SomeProtocol where Value: SomeProtocol {
    root[keyPath: keyPath] as SomeProtocol
}

func evaluateAndCast<Root, Value>(
    _ keyPath: KeyPath<Root, Value>,
    from root: Root
) -> SomeClass where Value: SomeClass {
    root[keyPath: keyPath] as SomeClass
}

And since this appears to be possible for all classes and protocols without Self or associatedtype requirements, it feels like it should be possible to make generic.

If this pitch does in fact cover this use case, it could be really powerful when combined with KeyPaths like this. I'd love to see it added to the language in the future.

3 Likes

Are there any updates on that feature? It is a quite major limitation in writing abstractions for now.

I thought I'd add my own use case to this thread.
We are creating a strong abstraction layer between modules where a module is only exposed through a public protocol-based interface and hides its concrete implementation. This is quite a challenge in Swift nowadays since it's not great at abstraction currently. One such challenge is our desire to expose a publisher through the interface which internally is backed by a subject, without exposing the subject publicly. To do so, I utilize the following property wrapper:

@propertyWrapper
public struct ErasedSubjectValue<Output : ErasedOutput, ErasedOutput> {
    public init(_ value: Output) {
        self.init(projectedValue: CurrentValueSubject<Output, Never>(value))
    }

    public init(projectedValue: CurrentValueSubject<Output, Never>) {
        self.projectedValue = projectedValue
        self.wrappedValue = projectedValue.map { $0 as ErasedOutput }
    }

    // - propertyWrapper
    public let wrappedValue:   CurrentValue<ErasedOutput, Never>
    public let projectedValue: CurrentValueSubject<Output, Never>
}

The result is a private projected value exposing the subject while the wrapped value exposes only a publisher of the subject. This generally works well, with one limitation:

Ideally I would also like to erase the implementation type of the value and expose only a public protocol that the value supports. I aim to do this by letting ErasedOutput be inferred from the wrappedValue while letting Output be inferred from the projectedValue and then up-casting the projected value back to its supertype in the erased publisher through a .map { $0 as ErasedOutput }. For this to work safely, I need to ensure the compiler is aware that ErasedOutput should be a supertype of Output.

Barring this feature, I am forced to remove the supertype constraint and change as into as!, which is obviously not great.