Namespacing protocols to other types

I don't agree with this, this behavior is exactly what I wanted in most of the cases. I want the nested type in protocol to not capture from the outer protocol (if it does not use Self or contain any associated types this is trivial), and I want the conforming types to inherit these nested types.

If you had this example:

protocol P {
  var b: B { get }
}
extension P {
  typealias B = Int
}

You certainly don't write P.B for the protocol requirements in the conforming type like so:

struct S: P {
  var b: P.B = 42
}

Instead you just want to write var b: B = 42. However you still want to re-use the namespacing for simple type nesting like so:

var test: P.B = 42 // this already works just fine today

To me your solution seems to be limited to protocols only (as this discussion originated from that direction), but it does not solve the same problem in non-protocol generic types. The attribute example I provides is just 'one solution' to the problem, but as far as I'm concerned it would cover both situation (generic types and protocols with Self or associated types).

Here is just one example ripped out of our library where I badly need nesting like mentioned above.

public protocol Peripheral {
  typealias Scan = PeripheralScan
  typealias Firmware = PeripheralFirmware
  typealias Service = PeripheralService
  typealias Characteristic = PeripheralCharacteristic
  typealias CharacteristicEndpoint = PeripheralCharacteristicEndpoint
  typealias ServiceEndpoint = PeripheralServiceEndpoint
  typealias Update = PeripheralUpdate
  ...
}

This is self-contradictory. @Slava_Pestov has explained that the natural semantics of declaring a type inside a protocol is to capture Self (even when Self is not used in the implementation of the type). He explained how this is a natural parallel with how types nested in a generic type capture the surrounding generic context whether they use it or not.

I gave an example that shows how you can declare a type nested inside a protocol _that does not capture Self`` by placing the types insideextension any Protocolinstead ofextension Protocol. If you don't want conformances to prefix the type names withProtocolI showed how you can add typealiases inextension some Protocol` that make the types directly available.

Why do you say this? My solution is to introduce existentials for generic types as well as protocols. any Array would be an Array with an unknown Element. This allows us to nest declarations inside Array without capturing the generic context.

You want these to be identical for each conformance, right? @Slava_Pestov explained why they would be distinct types for each conformance because they would capture Self if they were type declarations instead of typealiases.

Could a tastefully placed underscore be used to indicate not capturing generic context?

I have tweaked the type checker to allow that. I have also tweaked getContextSubstitutions() to substitute the types, although I am not sure if I have done that correctly.

protocol A {
  struct B {}
}

Inside IRGen, dumping the struct gives:

struct_decl range=[/Users/suyashsrijan/Desktop/test.swift:2:3 - line:2:13] "B" interface type='A.B.Type' access=internal non-resilient
  (constructor_decl implicit range=[/Users/suyashsrijan/Desktop/test.swift:2:10 - line:2:10] "init()" interface type='<Self where Self : A> (A.B.Type) -> () -> A.B' access=internal designated
    (parameter "self" interface type='A.B' inout)
    (parameter_list)
    (brace_stmt implicit
      (return_stmt implicit))))

I have also put emitNestedTypeDecls(protocol->getMembers()) inside emitProtocolDecl, however it has trouble emitting the metadata for the struct (crashes in type converter). If you change the struct to a class, then the crash is in useConformance (because the protocol conformance ref is invalid).

Seems like I am missing something or the type substitution isn't working correctly.

The type of 'self' inside a method of A.B must not be A.B, but Self.B where Self is the outer type parameter from the protocol A. However NominalType cannot model that right now.

I think a first step would be the planned changes to unify BoundGenericNominalType and NominalType, and change BoundGenericNominalType to store a substitution map instead of an array of generic arguments together with a parent type. Then the requisite changes to handle substitutions would be trivial.

1 Like

Hmm when will the planned changes happen? Is it worth waiting or could we just go with nested protocols first and build upon it later?

I don't think there are any concrete plans yet, but if you're interested, merging NominalType into BoundGenericNominalType would be a good first step.

And yeah, I think nesting protocols inside non-generic concrete types is much easier than the other direction and should not require any major representational changes.

2 Likes

This proposal has all our hearts desires about nesting protocols and the reverse: https://github.com/apple/swift-evolution/pull/552

Unfortunately it hasn't moved since Swift 3, even though everyone seems to agree about it there. I don't know why it was closed either for no good reason. Can we re-open it and push it along further?

Hi @Karl, your PR hasn‘t moved forward for several month. I wonder if you still intend to push it forward or would you like someone else to take over it (maybe @suyashsrijan)?!

Thank you for the update in advance.

I have something working, just need to write some tests. I’ll try to do that in the next few days.

It’s pretty basic - just putting protocols inside other types, but that’s a good start and would be nice to get it merged first before we think about putting other types inside protocols (esp. since its related to generic protocols).

4 Likes

That is still great news. Looking forward to it.

2 Likes

Is this topic dead ? I still have the need for namespacing everytime my project starts to reach a certain size.

1 Like

Everyone does. But any discussion inevitably sinks in "but what should we do with generics?" or something like that.

Modules already provide a good solution to namespacing.

If that's true then why are there so many uninhabited enums used as namespaces floating around?

4 Likes

They actually provide a fairly terrible solution. The only time they perform adequately is when using someone else's module. When writing your own (your app is a single module, by default), you might find that namespacing is useful for organization purposes, but that the overhead of a module is not worth the cost. This usually leads to language hacks such as uninhabited enums.

Personally, I have come across many instances where I had a collection of related static functions with which I did not want to pollute the global namespace. Also, I wanted it clear that the functions are indeed related. So I use uninhabited enums. I'm not going to create a module for half-a-dozen or fewer functions.

5 Likes

Currently the best hack to allow nesting protocols inside of other types is to use a typealias. So given the common MyView.Delegate use-case, you could write something like:

protocol _MyViewDelegate: AnyObject {
}

struct MyView {
  typealias Delegate = _MyViewDelegate
}

class SomeDelegate: MyView.Delegate { // works!
}

But yeah, it would be a lot nicer if we could write that the way we write other nested types. I also still really want nested types in protocols.

9 Likes

This post was flagged by the community and is temporarily hidden.

Karl's example has nothing to do with my use case, and your pejorative is unwarranted.

4 Likes

This looks like an excellent suggestion to me, cc: @Avi!

Terms of Service

Privacy Policy

Cookie Policy