Nested types in protocols (and nesting protocols in types)

I was just doing some googling, turns out there was a discussion about nesting protocols in other types that seemed to go positively a long time ago: [swift-evolution] [Pitch] Allow nested protocol declarations

I would additionally like to propose that protocols be allowed to contain nested types (including other protocols). Relevant ABI issue is that the standard library contains enums for “FloatingPointRoundingRule”, “FloatingPointClassification” and “FloatingPointSign”. They would probably be better expressed as “FloatingPoint.RoundingRule”, “.Sign", etc.

so to summarise, newly legal would be:

class MyClass {

    protocol Delegate {
    }
}

and also:

protocol MyProto {

    enum SomeValue {
    }

    protocol Delegate {
        associatedType ExpectedContent

  func receive(_: ExpectedContent, for: SomeValue)

        protocol SecondaryTarget {
            func receive(_ : ExpectedContent)
        }
    }
}

When conforming to a nested protocol, you can just use the name of the protocol:

class Host : MyProto.Delegate {
}

Except if a protocol in the chain has associated types, then you must use a concrete, conforming type instead (as you would in the first example — MyClass.Delegate):

class SecondaryProcessor : Host.SecondaryTarget {
}

If we’re good with this, I’ll write up a proposal.

- Karl

7 Likes

Extremely strong +1.

I’ve got a bunch of Objective-C-style really long type names of the sort “MyProtocol”, “MyProtocolSomeTypeEnum”, “MyProtocolSomeOptionsEnum”, etc. It would be really nice to make those Swiftier.

Charles

···

On Oct 17, 2016, at 12:59 PM, Karl via swift-evolution <swift-evolution@swift.org> wrote:

I was just doing some googling, turns out there was a discussion about nesting protocols in other types that seemed to go positively a long time ago: [swift-evolution] [Pitch] Allow nested protocol declarations

I would additionally like to propose that protocols be allowed to contain nested types (including other protocols). Relevant ABI issue is that the standard library contains enums for “FloatingPointRoundingRule”, “FloatingPointClassification” and “FloatingPointSign”. They would probably be better expressed as “FloatingPoint.RoundingRule”, “.Sign", etc.

so to summarise, newly legal would be:

class MyClass {

    protocol Delegate {
    }
}

and also:

protocol MyProto {

    enum SomeValue {
    }

    protocol Delegate {
        associatedType ExpectedContent

  func receive(_: ExpectedContent, for: SomeValue)

        protocol SecondaryTarget {
            func receive(_ : ExpectedContent)
        }
    }
}

When conforming to a nested protocol, you can just use the name of the protocol:

class Host : MyProto.Delegate {
}

Except if a protocol in the chain has associated types, then you must use a concrete, conforming type instead (as you would in the first example — MyClass.Delegate):

class SecondaryProcessor : Host.SecondaryTarget {
}

If we’re good with this, I’ll write up a proposal.

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

3 Likes

I like it!

I haven’t thought through the ramifications regarding associated types yet
though.

Is your vision that each conforming type would have to provide its own
nested type as specified by the protocol?

Or could the protocol itself define a nested type and anything could use it?

protocol FloatingPoint: … {
    enum RoundingRule {
        // Do I put an implementation here?
    }
}

Nevin

···

On Mon, Oct 17, 2016 at 1:59 PM, Karl via swift-evolution < swift-evolution@swift.org> wrote:

I was just doing some googling, turns out there was a discussion about
nesting protocols in other types that seemed to go positively a long time
ago: The swift-evolution Archives
Week-of-Mon-20160425/016074.html

I would additionally like to propose that protocols be allowed to contain
nested types (including other protocols). Relevant ABI issue is that the
standard library contains enums for “FloatingPointRoundingRule”,
“FloatingPointClassification” and “FloatingPointSign”. They would probably
be better expressed as “FloatingPoint.RoundingRule”, “.Sign", etc.

so to summarise, newly legal would be:

class MyClass {

    protocol Delegate {
    }
}

and also:

protocol MyProto {

    enum SomeValue {
    }

    protocol Delegate {
        associatedType ExpectedContent

func receive(_: ExpectedContent, for: SomeValue)

        protocol SecondaryTarget {
            func receive(_ : ExpectedContent)
        }
    }
}

When conforming to a nested protocol, you can just use the name of the
protocol:

class Host : MyProto.Delegate {
}

Except if a protocol in the chain has associated types, then you must use
a concrete, conforming type instead (as you would in the first example —
MyClass.Delegate):

class SecondaryProcessor : Host.SecondaryTarget {
}

If we’re good with this, I’ll write up a proposal.

- Karl

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

Two weeks ago there was a similar pitch here https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon–20161003/027643.html

In general I’m in favor of this feature, but I don’t know any technical issues this might have or does already have.

As I said in the other thread.

I’m for nested everything.

Nested extensions to reduce noise at some point, BUT this should not remove extension A.B { … } completely.

class A {
         
    class B { … }
         
    extension B { … } // extends A.B
}
We already have A.B syntax for extensions, why don’t we allow it for type declarations to reduce nesting (sometimes you don’t want to cluster everything)? Basically something like class A.B { … } would be a shortcut for extension A { class B { … } } and is bounded by the access modifier of A. (This is probably additive.)

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 19:59:54, Karl via swift-evolution (swift-evolution@swift.org) schrieb:

I was just doing some googling, turns out there was a discussion about nesting protocols in other types that seemed to go positively a long time ago: [swift-evolution] [Pitch] Allow nested protocol declarations

I would additionally like to propose that protocols be allowed to contain nested types (including other protocols). Relevant ABI issue is that the standard library contains enums for “FloatingPointRoundingRule”, “FloatingPointClassification” and “FloatingPointSign”. They would probably be better expressed as “FloatingPoint.RoundingRule”, “.Sign", etc.

so to summarise, newly legal would be:

class MyClass {

protocol Delegate \{
\}

}

and also:

protocol MyProto {

enum SomeValue \{
\}

protocol Delegate \{
    associatedType ExpectedContent

func receive(_: ExpectedContent, for: SomeValue)

    protocol SecondaryTarget \{
        func receive\(\_ : ExpectedContent\)
    \}
\}

}

When conforming to a nested protocol, you can just use the name of the protocol:

class Host : MyProto.Delegate {
}

Except if a protocol in the chain has associated types, then you must use a concrete, conforming type instead (as you would in the first example — MyClass.Delegate):

class SecondaryProcessor : Host.SecondaryTarget {
}

If we’re good with this, I’ll write up a proposal.

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

I like it!

I haven’t thought through the ramifications regarding associated types yet though.

Associated types are tricky. The example I gave maybe wasn’t the most practical, as to use it you’d have to write something like:

class A<T> : MyProto where T : A.Delegate, T.ExpectedContent == String {}

which then locks you to using one static type for your delegate. Moving the associatedType up a level to MyProto means we would implement it by writing:

class A : MyProto {
    typealias ExpectedContent = String

    var delegate : A.Delegate

    func notify() {
        delegate.receive(“a string”, for: .someCase)
    }
}

class B : MyProto {
    typealias ExpectedContent = Int

    var delegate : B.Delegate

    func notify() {
        delegate.receive(42, for: .someCase)
    }
}

Still, I think the general answer to protocols with associated types for this case is: “use a concrete type in its place”.

Is your vision that each conforming type would have to provide its own nested type as specified by the protocol?

Or could the protocol itself define a nested type and anything could use it?

protocol FloatingPoint: … {
    enum RoundingRule {
        // Do I put an implementation here?
    }
}

No, types which are defined inside the protocol are implemented there. Providing your own types to satisfy a conformance is what associated types are for.

If you wanted something like that, you could do it with a nested protocol + associated type:

protocol FloatingPoint {

    protocol _RoundingRule { func round(_ : Super) -> Super }
    associatedType RoundingRule : _RoundingRule
}

struct Float : FloatingPoint {

    enum RoundingRule : _RoundingRule {
        func round(_ val: Float) -> Float {
            /* switch self, perform rounding… */
        }
    }
}

That brings up an interesting point, though - we would need a way to refer to the outer protocol (I used “Super” here).

···

On 17 Oct 2016, at 20:13, Nevin Brackett-Rozinsky <nevin.brackettrozinsky@gmail.com> wrote:

Nevin

On Mon, Oct 17, 2016 at 1:59 PM, Karl via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
I was just doing some googling, turns out there was a discussion about nesting protocols in other types that seemed to go positively a long time ago: [swift-evolution] [Pitch] Allow nested protocol declarations

I would additionally like to propose that protocols be allowed to contain nested types (including other protocols). Relevant ABI issue is that the standard library contains enums for “FloatingPointRoundingRule”, “FloatingPointClassification” and “FloatingPointSign”. They would probably be better expressed as “FloatingPoint.RoundingRule”, “.Sign", etc.

so to summarise, newly legal would be:

class MyClass {

    protocol Delegate {
    }
}

and also:

protocol MyProto {

    enum SomeValue {
    }

    protocol Delegate {
        associatedType ExpectedContent

  func receive(_: ExpectedContent, for: SomeValue)

        protocol SecondaryTarget {
            func receive(_ : ExpectedContent)
        }
    }
}

When conforming to a nested protocol, you can just use the name of the protocol:

class Host : MyProto.Delegate {
}

Except if a protocol in the chain has associated types, then you must use a concrete, conforming type instead (as you would in the first example — MyClass.Delegate):

class SecondaryProcessor : Host.SecondaryTarget {
}

If we’re good with this, I’ll write up a proposal.

- Karl

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

I want this feature. Both class in protocol and protocol in class could
clean up many relationships but I think that a blocking concern is "How
does this interact with generics?" Nesting types in generic types is
already disallowed and how different is this feature from that?

···

On Mon, Oct 17, 2016 at 2:20 PM, Adrian Zubarev via swift-evolution < swift-evolution@swift.org> wrote:

Two weeks ago there was a similar pitch here https://lists.swift.org/
pipermail/swift-evolution/Week-of-Mon–20161003/027643.html
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161003/027643.html&gt;

In general I’m in favor of this feature, but I don’t know any technical
issues this might have or does already have.

As I said in the other thread.

   -

   I’m for nested everything.
   -

   Nested extensions to reduce noise at some point, BUT this should not
   remove extension A.B { … } completely.

   class A {

       class B { … }

       extension B { … } // extends A.B
   }

   -

   We already have A.B syntax for extensions, why don’t we allow it for
   type declarations to reduce nesting (sometimes you don’t want to cluster
   everything)? Basically something like class A.B { … } would be a
   shortcut for extension A { class B { … } } and is bounded by the
   access modifier of A. (This is probably additive.)

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 19:59:54, Karl via swift-evolution (
swift-evolution@swift.org) schrieb:

I was just doing some googling, turns out there was a discussion about
nesting protocols in other types that seemed to go positively a long time
ago: The swift-evolution Archives
Week-of-Mon-20160425/016074.html

I would additionally like to propose that protocols be allowed to contain
nested types (including other protocols). Relevant ABI issue is that the
standard library contains enums for “FloatingPointRoundingRule”,
“FloatingPointClassification” and “FloatingPointSign”. They would probably
be better expressed as “FloatingPoint.RoundingRule”, “.Sign", etc.

so to summarise, newly legal would be:

class MyClass {

    protocol Delegate {
    }
}

and also:

protocol MyProto {

    enum SomeValue {
    }

    protocol Delegate {
        associatedType ExpectedContent

func receive(_: ExpectedContent, for: SomeValue)

        protocol SecondaryTarget {
            func receive(_ : ExpectedContent)
        }
    }
}

When conforming to a nested protocol, you can just use the name of the
protocol:

class Host : MyProto.Delegate {
}

Except if a protocol in the chain has associated types, then you must use
a concrete, conforming type instead (as you would in the first example —
MyClass.Delegate):

class SecondaryProcessor : Host.SecondaryTarget {
}

If we’re good with this, I’ll write up a proposal.

- Karl
_______________________________________________
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

That’s also on the line: [swift/GenericsManifesto.md at 611fc78d28a5da97dd1bea40761b913b1077aef5 · apple/swift · GitHub](swift/GenericsManifesto.md at 611fc78d28a5da97dd1bea40761b913b1077aef5 · apple/swift · GitHub)

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 20:26:47, T.J. Usiyan (griotspeak@gmail.com) schrieb:

I want this feature. Both class in protocol and protocol in class could clean up many relationships but I think that a blocking concern is "How does this interact with generics?" Nesting types in generic types is already disallowed and how different is this feature from that?

That option should not be disallowed. Here is a simple example you might want to build at some point:

protocol ProtocolName {
     
    associatedtype AssociatedType
}

extension ProtocolName where AssociatedType == Int {
  
    struct InnerType {}
}

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 20:30:58, Karl via swift-evolution (swift-evolution@swift.org) schrieb:

Is your vision that each conforming type would have to provide its own nested type as specified by the protocol?

Or could the protocol itself define a nested type and anything could use it?

protocol FloatingPoint: … {
enum RoundingRule {
// Do I put an implementation here?
}
}

No, types which are defined inside the protocol are implemented there. Providing your own types to satisfy a conformance is what associated types are for.

If you wanted something like that, you could do it with a nested protocol + associated type:

protocol FloatingPoint {

protocol \_RoundingRule \{ func round\(\_ : Super\) \-&gt; Super \}
associatedType RoundingRule : \_RoundingRule

}

struct Float : FloatingPoint {

enum RoundingRule : \_RoundingRule \{
    func round\(\_ val: Float\) \-&gt; Float \{
        /\* switch self, perform rounding… \*/ 
    \}
\}

}

That brings up an interesting point, though - we would need a way to refer to the outer protocol (I used “Super” here).

And even partially implemented, behind the flag “-enable-experimental-nested-generic-types”. I don’t know what’s missing from it, but it seemed to work in some simple cases last time I tried.

- Karl

···

On 17 Oct 2016, at 20:31, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

That’s also on the line: [https://github.com/apple/swift/blob/611fc78d28a5da97dd1bea40761b913b1077aef5/docs/GenericsManifesto.md#nested-generics](https://github.com/apple/swift/blob/611fc78d28a5da97dd1bea40761b913b1077aef5/docs/GenericsManifesto.md#nested-generics)

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 20:26:47, T.J. Usiyan (griotspeak@gmail.com <mailto:griotspeak@gmail.com>) schrieb:

I want this feature. Both class in protocol and protocol in class could clean up many relationships but I think that a blocking concern is "How does this interact with generics?" Nesting types in generic types is already disallowed and how different is this feature from that?

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

But what would that mean? If I reference `ProtocolName.InnerType`, that doesn’t always have meaning. In fact, if you have two different extensions where AssociatedType equals something else, there’s a type ambiguity from other code. I suspect it would only work if that InnerType was mandated to be `private`.

···

On Oct 17, 2016, at 12:44 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

That option should not be disallowed. Here is a simple example you might want to build at some point:

protocol ProtocolName {
     
    associatedtype AssociatedType
}

extension ProtocolName where AssociatedType == Int {
  
    struct InnerType {}
}

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 20:30:58, Karl via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

Is your vision that each conforming type would have to provide its own nested type as specified by the protocol?

Or could the protocol itself define a nested type and anything could use it?

protocol FloatingPoint: … {
    enum RoundingRule {
        // Do I put an implementation here?
    }
}

No, types which are defined inside the protocol are implemented there. Providing your own types to satisfy a conformance is what associated types are for.

If you wanted something like that, you could do it with a nested protocol + associated type:

protocol FloatingPoint {

    protocol _RoundingRule { func round(_ : Super) -> Super }
    associatedType RoundingRule : _RoundingRule
}

struct Float : FloatingPoint {

    enum RoundingRule : _RoundingRule {
        func round(_ val: Float) -> Float {
            /* switch self, perform rounding… */
        }
    }
}

That brings up an interesting point, though - we would need a way to refer to the outer protocol (I used “Super” here).

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

I had a go at writing this up formally:

Is there anything I missed?

- Karl

···

On 17 Oct 2016, at 20:44, Adrian Zubarev <adrian.zubarev@devandartist.com> wrote:

That option should not be disallowed. Here is a simple example you might want to build at some point:

protocol ProtocolName {
     
    associatedtype AssociatedType
}

extension ProtocolName where AssociatedType == Int {
  
    struct InnerType {}
}

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 20:30:58, Karl via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

Is your vision that each conforming type would have to provide its own nested type as specified by the protocol?

Or could the protocol itself define a nested type and anything could use it?

protocol FloatingPoint: … {
    enum RoundingRule {
        // Do I put an implementation here?
    }
}

No, types which are defined inside the protocol are implemented there. Providing your own types to satisfy a conformance is what associated types are for.

If you wanted something like that, you could do it with a nested protocol + associated type:

protocol FloatingPoint {

    protocol _RoundingRule { func round(_ : Super) -> Super }
    associatedType RoundingRule : _RoundingRule
}

struct Float : FloatingPoint {

    enum RoundingRule : _RoundingRule {
        func round(_ val: Float) -> Float {
            /* switch self, perform rounding… */
        }
    }
}

That brings up an interesting point, though - we would need a way to refer to the outer protocol (I used “Super” here).

But what would that mean? If I reference `ProtocolName.InnerType`, that doesn’t always have meaning. In fact, if you have two different extensions where AssociatedType equals something else, there’s a type ambiguity from other code. I suspect it would only work if that InnerType was mandated to be `private`.

You would need a reference to a (ProtocolName where AssociatedType == Int), which you can get either from a `self` inside the extension or from a generic parameter:

struct MyValue<T> : ProtocolName { typealias AssociatedType = T }

let _ = MyValue<Int>().InnerType()

···

On 22 Oct 2016, at 04:02, Braeden Profile <jhaezhyr12@gmail.com> wrote:

On Oct 17, 2016, at 12:44 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

That option should not be disallowed. Here is a simple example you might want to build at some point:

protocol ProtocolName {
     
    associatedtype AssociatedType
}

extension ProtocolName where AssociatedType == Int {
  
    struct InnerType {}
}

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 20:30:58, Karl via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

Is your vision that each conforming type would have to provide its own nested type as specified by the protocol?

Or could the protocol itself define a nested type and anything could use it?

protocol FloatingPoint: … {
    enum RoundingRule {
        // Do I put an implementation here?
    }
}

No, types which are defined inside the protocol are implemented there. Providing your own types to satisfy a conformance is what associated types are for.

If you wanted something like that, you could do it with a nested protocol + associated type:

protocol FloatingPoint {

    protocol _RoundingRule { func round(_ : Super) -> Super }
    associatedType RoundingRule : _RoundingRule
}

struct Float : FloatingPoint {

    enum RoundingRule : _RoundingRule {
        func round(_ val: Float) -> Float {
            /* switch self, perform rounding… */
        }
    }
}

That brings up an interesting point, though - we would need a way to refer to the outer protocol (I used “Super” here).

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

That’s also on the line: [https://github.com/apple/swift/blob/611fc78d28a5da97dd1bea40761b913b1077aef5/docs/GenericsManifesto.md#nested-generics](https://github.com/apple/swift/blob/611fc78d28a5da97dd1bea40761b913b1077aef5/docs/GenericsManifesto.md#nested-generics)

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 20:26:47, T.J. Usiyan (griotspeak@gmail.com <mailto:griotspeak@gmail.com>) schrieb:

I want this feature. Both class in protocol and protocol in class could clean up many relationships but I think that a blocking concern is "How does this interact with generics?" Nesting types in generic types is already disallowed and how different is this feature from that?

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

And even partially implemented, behind the flag “-enable-experimental-nested-generic-types”. I don’t know what’s missing from it, but it seemed to work in some simple cases last time I tried.

This flag implements nesting non-protocol generic types inside other non-protocol generic types, except for some bugs I need to fix before I can just enable this all by default.

However protocols nested inside types and types nested inside protocols is still not supported, because protocols introduce a separate series of issues involving associated types and the ’Self’ type.

The hard part of getting nested generics right is what to do if a nested type ‘captures’ generic parameters of the outer type. For non-protocol types, the behavior here is pretty straightforward.

If we allow protocols to be nested inside other types, we have to decide what to do if the protocol ‘closes over’ generic parameters of the outer type. For example,

struct A<T> {
  protocol P {
    func requirement() -> T
  }
}

Presumably A<Int>.P and A<String>.P are distinct types, and A.P has a hidden associated type corresponding to the type parameter ’T’?

The other case is problematic too — the nested type might refer to an associated type of the outer protocol:

protocol P {
  associatedtype A

  struct T {
    var value: A
  }
}

Now writing P.T does not make sense, for the same reason that we cannot form an existential of type P.A. We could prohibit references to outer associated types of this form, or we could figure out some way to give it a meaning. If C is a concrete type conforming to P, then certainly C.T makes sense, for instance. Internally, the nested type A.T could have a hidden ‘Self’ generic type parameter, so that writing C.T is really the same as P.T<C>.

Protocols nested inside protocols also have the same issue.

Also note that even with the experimental flag, generic types nested inside generic *functions* are not supported either. Here, again, the difficulty is with captured generic parameters. The semantics are clear but the implementation needs some work.

Slava

···

On Oct 17, 2016, at 11:35 AM, Karl via swift-evolution <swift-evolution@swift.org> wrote:

On 17 Oct 2016, at 20:31, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

Hi Karl,

I just saw your draft after sending my first reply earlier; I posted some additional comments on the gist.

Also I remembered that Jordan Rose and I were talking about this recently in the context of the ClangImporter, so I'm CCing him in case he wants to share some thoughts.

···

On Oct 21, 2016, at 6:21 PM, Karl via swift-evolution <swift-evolution@swift.org> wrote:

I had a go at writing this up formally:

ease-nesting-protocol-restrictions.md · GitHub

Is there anything I missed?

- Karl

On 17 Oct 2016, at 20:44, Adrian Zubarev <adrian.zubarev@devandartist.com <mailto:adrian.zubarev@devandartist.com>> wrote:

That option should not be disallowed. Here is a simple example you might want to build at some point:

protocol ProtocolName {
     
    associatedtype AssociatedType
}

extension ProtocolName where AssociatedType == Int {
  
    struct InnerType {}
}

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 20:30:58, Karl via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

Is your vision that each conforming type would have to provide its own nested type as specified by the protocol?

Or could the protocol itself define a nested type and anything could use it?

protocol FloatingPoint: … {
    enum RoundingRule {
        // Do I put an implementation here?
    }
}

No, types which are defined inside the protocol are implemented there. Providing your own types to satisfy a conformance is what associated types are for.

If you wanted something like that, you could do it with a nested protocol + associated type:

protocol FloatingPoint {

    protocol _RoundingRule { func round(_ : Super) -> Super }
    associatedType RoundingRule : _RoundingRule
}

struct Float : FloatingPoint {

    enum RoundingRule : _RoundingRule {
        func round(_ val: Float) -> Float {
            /* switch self, perform rounding… */
        }
    }
}

That brings up an interesting point, though - we would need a way to refer to the outer protocol (I used “Super” here).

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

But what would that mean? If I reference `ProtocolName.InnerType`, that doesn’t always have meaning. In fact, if you have two different extensions where AssociatedType equals something else, there’s a type ambiguity from other code. I suspect it would only work if that InnerType was mandated to be `private`.

You would need a reference to a (ProtocolName where AssociatedType == Int), which you can get either from a `self` inside the extension or from a generic parameter:

struct MyValue<T> : ProtocolName { typealias AssociatedType = T }

let _ = MyValue<Int>().InnerType()

No, wait - sorry, that’s wrong. I got confused for a second. You’re right; it would have to be a private type.

···

On 22 Oct 2016, at 04:07, Karl <raziel.im+swift-evo@gmail.com> wrote:

On 22 Oct 2016, at 04:02, Braeden Profile <jhaezhyr12@gmail.com <mailto:jhaezhyr12@gmail.com>> wrote:

On Oct 17, 2016, at 12:44 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

That option should not be disallowed. Here is a simple example you might want to build at some point:

protocol ProtocolName {
     
    associatedtype AssociatedType
}

extension ProtocolName where AssociatedType == Int {
  
    struct InnerType {}
}

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 20:30:58, Karl via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

Is your vision that each conforming type would have to provide its own nested type as specified by the protocol?

Or could the protocol itself define a nested type and anything could use it?

protocol FloatingPoint: … {
    enum RoundingRule {
        // Do I put an implementation here?
    }
}

No, types which are defined inside the protocol are implemented there. Providing your own types to satisfy a conformance is what associated types are for.

If you wanted something like that, you could do it with a nested protocol + associated type:

protocol FloatingPoint {

    protocol _RoundingRule { func round(_ : Super) -> Super }
    associatedType RoundingRule : _RoundingRule
}

struct Float : FloatingPoint {

    enum RoundingRule : _RoundingRule {
        func round(_ val: Float) -> Float {
            /* switch self, perform rounding… */
        }
    }
}

That brings up an interesting point, though - we would need a way to refer to the outer protocol (I used “Super” here).

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

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.

Here’s a real life example pared down. I wish I could do this:

    public struct ResponseContentTransformer<InputContentType, OutputContentType>: ResponseTransformer {

      public init(onInputTypeMismatch mismatchAction: InputTypeMismatchAction = .error) {
        ...
      }

      public enum InputTypeMismatchAction { // Does not depend on generic types above
        case error
        case skip
        case skipIfOutputTypeMatches
      }

    }

InputTypeMismatchAction is tightly associated with ResponseContentTransformer, and is confusing as a top-level type.

What do you think about providing a “no captures” modifier for nested types — like static inner classes in Java? Then Swift could provide the namespace nesting I wish for this without having to resolve the trickier type capture questions yet.

Alternatively, what if (1) outer types aren’t capture unless they’re referenced, and (2) nesting is only illegal if there’s a capture? Then my code above would compile, as would this:

    public struct S<T> {
      public enum Foo {
        case yin
        case yang
      }
    }

…but this wouldn’t:

    public struct S<T> {
      public enum Foo {
        case yin(thing: T) // capture of T illegal (for now)
        case yang
      }
    }

Either of these approaches would allow hygienic namespacing now while leaving the door open to outer type capture in the future.

Cheers,

Paul

···

On Oct 24, 2016, at 5:09 AM, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

However protocols nested inside types and types nested inside protocols is still not supported, because protocols introduce a separate series of issues involving associated types and the ’Self’ type.

The hard part of getting nested generics right is what to do if a nested type ‘captures’ generic parameters of the outer type. For non-protocol types, the behavior here is pretty straightforward.

If we allow protocols to be nested inside other types, we have to decide what to do if the protocol ‘closes over’ generic parameters of the outer type. For example,

struct A<T> {
  protocol P {
    func requirement() -> T
  }
}

Presumably A<Int>.P and A<String>.P are distinct types, and A.P has a hidden associated type corresponding to the type parameter ’T’?

The other case is problematic too — the nested type might refer to an associated type of the outer protocol:

protocol P {
  associatedtype A

  struct T {
    var value: A
  }
}

Now writing P.T does not make sense, for the same reason that we cannot form an existential of type P.A. We could prohibit references to outer associated types of this form, or we could figure out some way to give it a meaning. If C is a concrete type conforming to P, then certainly C.T makes sense, for instance. Internally, the nested type A.T could have a hidden ‘Self’ generic type parameter, so that writing C.T is really the same as P.T<C>.

Protocols nested inside protocols also have the same issue.

But what would that mean? If I reference `ProtocolName.InnerType`, that doesn’t always have meaning. In fact, if you have two different extensions where AssociatedType equals something else, there’s a type ambiguity from other code. I suspect it would only work if that InnerType was mandated to be `private`.

You would need a reference to a (ProtocolName where AssociatedType == Int), which you can get either from a `self` inside the extension or from a generic parameter:

struct MyValue<T> : ProtocolName { typealias AssociatedType = T }

let _ = MyValue<Int>().InnerType()

No, wait - sorry, that’s wrong. I got confused for a second. You’re right; it would have to be a private type.

Actually I think I take that back (I was just writing, lots of snippets floating around my head) - ProtocolName is a generic protocol, so types inside of it would become types on the concrete conformers. That’s consistent with the Editor.Delegate example in the draft proposal I linked to.

So MyValue<Int>.InnerType would exist :+1: ProtocolName.InnerType isn’t really very meaningful otherwise.

- Karl

···

On 22 Oct 2016, at 04:12, Karl <raziel.im+swift-evo@gmail.com> wrote:

On 22 Oct 2016, at 04:07, Karl <raziel.im+swift-evo@gmail.com <mailto:raziel.im+swift-evo@gmail.com>> wrote:

On 22 Oct 2016, at 04:02, Braeden Profile <jhaezhyr12@gmail.com <mailto:jhaezhyr12@gmail.com>> wrote:

On Oct 17, 2016, at 12:44 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

That option should not be disallowed. Here is a simple example you might want to build at some point:

protocol ProtocolName {
     
    associatedtype AssociatedType
}

extension ProtocolName where AssociatedType == Int {
  
    struct InnerType {}
}

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 20:30:58, Karl via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) schrieb:

Is your vision that each conforming type would have to provide its own nested type as specified by the protocol?

Or could the protocol itself define a nested type and anything could use it?

protocol FloatingPoint: … {
    enum RoundingRule {
        // Do I put an implementation here?
    }
}

No, types which are defined inside the protocol are implemented there. Providing your own types to satisfy a conformance is what associated types are for.

If you wanted something like that, you could do it with a nested protocol + associated type:

protocol FloatingPoint {

    protocol _RoundingRule { func round(_ : Super) -> Super }
    associatedType RoundingRule : _RoundingRule
}

struct Float : FloatingPoint {

    enum RoundingRule : _RoundingRule {
        func round(_ val: Float) -> Float {
            /* switch self, perform rounding… */
        }
    }
}

That brings up an interesting point, though - we would need a way to refer to the outer protocol (I used “Super” here).

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

1 Like

However protocols nested inside types and types nested inside protocols is still not supported, because protocols introduce a separate series of issues involving associated types and the ’Self’ type.

The hard part of getting nested generics right is what to do if a nested type ‘captures’ generic parameters of the outer type. For non-protocol types, the behavior here is pretty straightforward.

If we allow protocols to be nested inside other types, we have to decide what to do if the protocol ‘closes over’ generic parameters of the outer type. For example,

struct A<T> {
protocol P {
   func requirement() -> T
}
}

Presumably A<Int>.P and A<String>.P are distinct types, and A.P has a hidden associated type corresponding to the type parameter ’T’?

The other case is problematic too — the nested type might refer to an associated type of the outer protocol:

protocol P {
associatedtype A

struct T {
   var value: A
}
}

Now writing P.T does not make sense, for the same reason that we cannot form an existential of type P.A. We could prohibit references to outer associated types of this form, or we could figure out some way to give it a meaning. If C is a concrete type conforming to P, then certainly C.T makes sense, for instance. Internally, the nested type A.T could have a hidden ‘Self’ generic type parameter, so that writing C.T is really the same as P.T<C>.

Protocols nested inside protocols also have the same issue.

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.

Here’s a real life example pared down. I wish I could do this:

   public struct ResponseContentTransformer<InputContentType, OutputContentType>: ResponseTransformer {

     public init(onInputTypeMismatch mismatchAction: InputTypeMismatchAction = .error) {
       ...
     }

     public enum InputTypeMismatchAction { // Does not depend on generic types above
       case error
       case skip
       case skipIfOutputTypeMatches
     }

   }

InputTypeMismatchAction is tightly associated with ResponseContentTransformer, and is confusing as a top-level type.

What do you think about providing a “no captures” modifier for nested types — like static inner classes in Java? Then Swift could provide the namespace nesting I wish for this without having to resolve the trickier type capture questions yet.

Alternatively, what if (1) outer types aren’t capture unless they’re referenced, and (2) nesting is only illegal if there’s a capture? Then my code above would compile, as would this:

   public struct S<T> {
     public enum Foo {
       case yin
       case yang
     }
   }

…but this wouldn’t:

   public struct S<T> {
     public enum Foo {
       case yin(thing: T) // capture of T illegal (for now)
       case yang
     }
   }

Either of these approaches would allow hygienic namespacing now while leaving the door open to outer type capture in the future.

Yeah, this makes sense for a first cut at this feature.

Slava

···

On Oct 24, 2016, at 8:12 AM, Paul Cantrell <cantrell@pobox.com> wrote:

On Oct 24, 2016, at 5:09 AM, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

Cheers,

Paul

First of all, thank you for writing the formal proposal.

To begin with, I’d like to remember you that type nesting is not only done through nesting types directly in concrete types. A commonly used pattern to reduce clustering is nesting inside extensions:

extension TypeName {
     
    class InnerTypeName { … }
}
Next up:

protocol Outer {
     protocol Inner {
         associatedtype Huh // why would you do this?
     }
}
I disagree with this: why would you want to restrict this?

The Outer protocol might be simple by the inner protocol is not. If you’d move the associatedtype Huh into Outer protocol you’d automatically break the intended simplicity of the Outer protocol.

The following example is kept simple just to show that the outer protocol is in our case intended to be simple where the inner is not.

protocol Building {
     
    protocol Room {

        associatedtype Person
    }

    var numberOfRooms: Int { get }
}

struct Pupil {}
struct Student {}

struct University : Building {
     
    let numberOfRooms: Int = 1000

    struct Room : Building.Room {

        typealias Person = Student
    }
}

struct School : Building {
     
    let numberOfRooms: Int = 100

    struct Room : Building.Room {

        typealias Person = Pupil
    }
}

let buildings: [Building] = [University(), School()] // fine because `Building` is simple
The inner types in this example are different from each other, but this wasn’t the main point anyways.

Unless this restriction would have other technical reasons than “It is hard to think of a valid reason why a nested protocol might want to be more generic than its parent”, I’m against it.

At the very beginning I said that nesting is also done through extensions. Let’s rewrite the above example from your draft that way and see what we good.

protocol Outer {} // Simple

extension Outer {
     
    // Complex
    protocol Inner {
     associatedtype Huh // why would you do this?
  }
}

// new name spacing `Outer.Inner`
From this perspective it’s much clearer now that the Outer protocol type in out case is intended to be simple.

About associatedtypes with extensions:

In Swift 3.0 this code compiles:

public class Class {}

public protocol Outer {
    associatedtype AAA : Class
}

extension Outer {
    public typealias AAA = Int
}
This does mean that the associatedtype is lost in extensions and can be reused as an inner type for nesting. This also means that the inner type wouldn’t be able to capture the associatedtype from the outer type and might be simple.

protocol Outer {

   associatedtype AAA
}

extension Outer {
    protocol Inner {} // Simple because nesting from within extensions seems to ignore the associatedtype from the `Outer` type
}
You can compare this to todays workarounds where you only want to create a nice name space but want to keep the inner type simple.

// Implementation artifact
protocol _Inner {} // simple

// Complex
protocol Outer {
    associatedtype Anything
}

extension Outer {
    typealias Inner = _Inner
}

// Free to use `Outer.Inner`

···

--
Adrian Zubarev
Sent with Airmail

Am 22. Oktober 2016 um 04:18:25, Karl (razielim@gmail.com) schrieb:

On 22 Oct 2016, at 04:12, Karl <raziel.im+swift-evo@gmail.com> wrote:

On 22 Oct 2016, at 04:07, Karl <raziel.im+swift-evo@gmail.com> wrote:

On 22 Oct 2016, at 04:02, Braeden Profile <jhaezhyr12@gmail.com> wrote:

But what would that mean? If I reference `ProtocolName.InnerType`, that doesn’t always have meaning. In fact, if you have two different extensions where AssociatedType equals something else, there’s a type ambiguity from other code. I suspect it would only work if that InnerType was mandated to be `private`.

You would need a reference to a (ProtocolName where AssociatedType == Int), which you can get either from a `self` inside the extension or from a generic parameter:

struct MyValue<T> : ProtocolName { typealias AssociatedType = T }

let _ = MyValue<Int>().InnerType()

No, wait - sorry, that’s wrong. I got confused for a second. You’re right; it would have to be a private type.

Actually I think I take that back (I was just writing, lots of snippets floating around my head) - ProtocolName is a generic protocol, so types inside of it would become types on the concrete conformers. That’s consistent with the Editor.Delegate example in the draft proposal I linked to.

So MyValue<Int>.InnerType would exist :+1: ProtocolName.InnerType isn’t really very meaningful otherwise.

- Karl

On Oct 17, 2016, at 12:44 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

That option should not be disallowed. Here is a simple example you might want to build at some point:

protocol ProtocolName {
      
    associatedtype AssociatedType
}

extension ProtocolName where AssociatedType == Int {
   
    struct InnerType {}
}

--
Adrian Zubarev
Sent with Airmail

Am 17. Oktober 2016 um 20:30:58, Karl via swift-evolution (swift-evolution@swift.org) schrieb:

Is your vision that each conforming type would have to provide its own nested type as specified by the protocol?

Or could the protocol itself define a nested type and anything could use it?

protocol FloatingPoint: … {
enum RoundingRule {
// Do I put an implementation here?
}
}

No, types which are defined inside the protocol are implemented there. Providing your own types to satisfy a conformance is what associated types are for.

If you wanted something like that, you could do it with a nested protocol + associated type:

protocol FloatingPoint {

protocol \_RoundingRule \{ func round\(\_ : Super\) \-&gt; Super \}
associatedType RoundingRule : \_RoundingRule

}

struct Float : FloatingPoint {

enum RoundingRule : \_RoundingRule \{
    func round\(\_ val: Float\) \-&gt; Float \{
        /\* switch self, perform rounding… \*/ 
    \}
\}

}

That brings up an interesting point, though - we would need a way to refer to the outer protocol (I used “Super” here).

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

Should I take a crack at writing up a proposal for this? Now? After ABI work is done? (Probably the latter “OK if no captures” approach?) Eager to help; don’t want to be in the way.

P

···

On Oct 24, 2016, at 4:43 PM, Slava Pestov <spestov@apple.com> wrote:

On Oct 24, 2016, at 8:12 AM, Paul Cantrell <cantrell@pobox.com> wrote:

On Oct 24, 2016, at 5:09 AM, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

However protocols nested inside types and types nested inside protocols is still not supported, because protocols introduce a separate series of issues involving associated types and the ’Self’ type.

The hard part of getting nested generics right is what to do if a nested type ‘captures’ generic parameters of the outer type. For non-protocol types, the behavior here is pretty straightforward.

If we allow protocols to be nested inside other types, we have to decide what to do if the protocol ‘closes over’ generic parameters of the outer type. For example,

struct A<T> {
protocol P {
  func requirement() -> T
}
}

Presumably A<Int>.P and A<String>.P are distinct types, and A.P has a hidden associated type corresponding to the type parameter ’T’?

The other case is problematic too — the nested type might refer to an associated type of the outer protocol:

protocol P {
associatedtype A

struct T {
  var value: A
}
}

Now writing P.T does not make sense, for the same reason that we cannot form an existential of type P.A. We could prohibit references to outer associated types of this form, or we could figure out some way to give it a meaning. If C is a concrete type conforming to P, then certainly C.T makes sense, for instance. Internally, the nested type A.T could have a hidden ‘Self’ generic type parameter, so that writing C.T is really the same as P.T<C>.

Protocols nested inside protocols also have the same issue.

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.

Here’s a real life example pared down. I wish I could do this:

  public struct ResponseContentTransformer<InputContentType, OutputContentType>: ResponseTransformer {

    public init(onInputTypeMismatch mismatchAction: InputTypeMismatchAction = .error) {
      ...
    }

    public enum InputTypeMismatchAction { // Does not depend on generic types above
      case error
      case skip
      case skipIfOutputTypeMatches
    }

  }

InputTypeMismatchAction is tightly associated with ResponseContentTransformer, and is confusing as a top-level type.

What do you think about providing a “no captures” modifier for nested types — like static inner classes in Java? Then Swift could provide the namespace nesting I wish for this without having to resolve the trickier type capture questions yet.

Alternatively, what if (1) outer types aren’t capture unless they’re referenced, and (2) nesting is only illegal if there’s a capture? Then my code above would compile, as would this:

  public struct S<T> {
    public enum Foo {
      case yin
      case yang
    }
  }

…but this wouldn’t:

  public struct S<T> {
    public enum Foo {
      case yin(thing: T) // capture of T illegal (for now)
      case yang
    }
  }

Either of these approaches would allow hygienic namespacing now while leaving the door open to outer type capture in the future.

Yeah, this makes sense for a first cut at this feature.

Slava