[Pitch] Nested types in protocols (and nesting protocols in types)


(Jon Hull) #1

FWIW, in almost all the situations where I’ve wanted to nest types inside protocols and generic types, it’s only as a namespacing convenience. Most often, it’s an enum type that’s used only by a single method, and having it at the top of the module namespace adds clutter.
Alternatively, what if (1) outer types aren’t capture unless they’re referenced, and (2) nesting is only illegal if there’s a capture?

I was just about to write something similar, but Paul said it better.

Why don’t we allow everything to be nested in everything for namespacing purposes (which is most of why I want it), but forbid references to generic or associated types from the nested type? Then, as the generics manifesto gets implemented, we can start allowing certain generics to be referenced over time as they start to become possible and make sense (but we wouldn’t worry about that yet for this proposal)

Is there another issue which would keep that simpler version from working?

Thanks,
Jon


(Karl) #2

Yeah, that’s why I structured it like that. We can’t represent most of the stuff with captured associated types in the type system right now.

I’ll split it up to include just the simple cases, with some rules:

- Protocols cannot capture generic type parameters
- No nested types can capture associated types

@Slava: Yes, I thought about a system where protocols which capture associated types could only be referred to from a concrete conforming type:

protocol Editor {
    associatedtype Content

    protocol Delegate : class { func contentDidChange(oldContent: Content) }
    var delegate : Delegate? { get, set }
}

class ImageEditor : Editor {
    associatedtype Content = Image
    weak var delegate : Delegate? // type is ImageEditor.Delegate, not Editor.Delegate
}

class ImageEditorController : ImageEditor.Delegate {
    func contentDidChange(oldContent: Image) {
    }
}

class TextEditorController : TextEditor.Delegate {
    func contentDidChange(oldContent: String) {
    }
}

And even though that seems pretty cool and like it adds some value, but it seemed like people would want to be able to express “This is a Delegate for any Editor whose Content is Image” rather than only “This is a Delegate for an ImageEditor”. Perhaps that's something which could live alongside a fully existential-based model (i.e. conforming to Editor.Delegate with an inherited associatedtype “Content”, as in the proposal) until we can do that.

Conversion to generic types is also interesting. I thought about perhaps requiring a shorthand for captured types:

protocol MyProtocol {
    associatedtype ErrorType : Error

    enum NestedType<associatedtype ErrorType> { // binds generic parameter ErrorType to associated type ErrorType
        case success
        case failed(ErrorType)
    }

    func getProgress() -> NestedType // behind-the-scenes when implemented: func getProgress() -> NestedType<Self.ErrorType>
    func setProgress(_: NestedType) // behind-the-scenes when implemented: func setProgress<E>(_: NestedType<E>) where E == Self.ErrorType
}

On the one hand, it’s a bit of an awkward cludge to work-around gaps in existential support; on the other hand, automatic bridging between existentials and bound generics might be a good feature anyway, because you know more about the generic type and can optimise more.

- Karl

···

On 24 Oct 2016, at 20:23, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

FWIW, in almost all the situations where I’ve wanted to nest types inside protocols and generic types, it’s only as a namespacing convenience. Most often, it’s an enum type that’s used only by a single method, and having it at the top of the module namespace adds clutter.
Alternatively, what if (1) outer types aren’t capture unless they’re referenced, and (2) nesting is only illegal if there’s a capture?

I was just about to write something similar, but Paul said it better.

Why don’t we allow everything to be nested in everything for namespacing purposes (which is most of why I want it), but forbid references to generic or associated types from the nested type? Then, as the generics manifesto gets implemented, we can start allowing certain generics to be referenced over time as they start to become possible and make sense (but we wouldn’t worry about that yet for this proposal)

Is there another issue which would keep that simpler version from working?

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


(Karl) #3

Sorry, I don’t mean “automatic bridging” — I mean a syntax that makes using generic types as simple as it would be to use an existential.

For example, it might be really tempting to just capture “ErrorType” directly instead of indirectly through the generic constraint (especially with people always looking for code which is "more dynamic”), but it would be more awkward to use in lots of cases and less optimisable. OTOH, generics need lots of boilerplate. If you can bind a generic parameter for a supporting type with one of your own associated types though, the compiler can generate the boilerplate for you.

- Karl

···

On 25 Oct 2016, at 00:06, Karl <raziel.im+swift-users@gmail.com> wrote:

On 24 Oct 2016, at 20:23, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

FWIW, in almost all the situations where I’ve wanted to nest types inside protocols and generic types, it’s only as a namespacing convenience. Most often, it’s an enum type that’s used only by a single method, and having it at the top of the module namespace adds clutter.
Alternatively, what if (1) outer types aren’t capture unless they’re referenced, and (2) nesting is only illegal if there’s a capture?

I was just about to write something similar, but Paul said it better.

Why don’t we allow everything to be nested in everything for namespacing purposes (which is most of why I want it), but forbid references to generic or associated types from the nested type? Then, as the generics manifesto gets implemented, we can start allowing certain generics to be referenced over time as they start to become possible and make sense (but we wouldn’t worry about that yet for this proposal)

Is there another issue which would keep that simpler version from working?

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

Yeah, that’s why I structured it like that. We can’t represent most of the stuff with captured associated types in the type system right now.

I’ll split it up to include just the simple cases, with some rules:

- Protocols cannot capture generic type parameters
- No nested types can capture associated types

@Slava: Yes, I thought about a system where protocols which capture associated types could only be referred to from a concrete conforming type:

protocol Editor {
    associatedtype Content

    protocol Delegate : class { func contentDidChange(oldContent: Content) }
    var delegate : Delegate? { get, set }
}

class ImageEditor : Editor {
    associatedtype Content = Image
    weak var delegate : Delegate? // type is ImageEditor.Delegate, not Editor.Delegate
}

class ImageEditorController : ImageEditor.Delegate {
    func contentDidChange(oldContent: Image) {
    }
}

class TextEditorController : TextEditor.Delegate {
    func contentDidChange(oldContent: String) {
    }
}

And even though that seems pretty cool and like it adds some value, but it seemed like people would want to be able to express “This is a Delegate for any Editor whose Content is Image” rather than only “This is a Delegate for an ImageEditor”. Perhaps that's something which could live alongside a fully existential-based model (i.e. conforming to Editor.Delegate with an inherited associatedtype “Content”, as in the proposal) until we can do that.

Conversion to generic types is also interesting. I thought about perhaps requiring a shorthand for captured types:

protocol MyProtocol {
    associatedtype ErrorType : Error

    enum NestedType<associatedtype ErrorType> { // binds generic parameter ErrorType to associated type ErrorType
        case success
        case failed(ErrorType)
    }

    func getProgress() -> NestedType // behind-the-scenes when implemented: func getProgress() -> NestedType<Self.ErrorType>
    func setProgress(_: NestedType) // behind-the-scenes when implemented: func setProgress<E>(_: NestedType<E>) where E == Self.ErrorType
}

On the one hand, it’s a bit of an awkward cludge to work-around gaps in existential support; on the other hand, automatic bridging between existentials and bound generics might be a good feature anyway, because you know more about the generic type and can optimise more.

- Karl