As @jrose has correctly pointed out the term "submodule" is quite ambiguous, so I will not use that term to refer to my idea, but instead refer to it by the more specific term: "namespace"
Current State of the Art
Currently, in order to create a standalone namespace smaller than a module, one must create an enum with no cases (aka an 'empty' enum). This has a few problems:
The namespace looks like a type to coders unfamilar with the use of enum as a namespace, and to further confusion can be extended to conform to protocols.
All non-type declarations inside the enum must be marked static which adds boilerplate and reduces readability.
There's no way to specify that a member of a nested type should only be visible within its enclosing enum.
Proposed Solution
Introduce a new declaration namespace. This acts as an empty enum except as follows:
It may not be used as a type in any context, including generic parameters. Nor may it conform to protocols.
A namespace may not be generic and may not be nested inside of a generic type.
All non-type declarations are effectively static. Explicitly specifying a top-level namespace variable as static is an error.
Declarations with less than public access in nested types or namespaces are only visible within the enclosing namespace or an extension of that namespace, which must still be inside the declaring module or file for internal and (file)private declarations respectively.
Protocols may be declared inside of a namespace (we might want to allow that in type declarations in general).
A namespace may only be extended from the module it is declared in (although the types inside it may be extended as normal).
Example of Use
public namespace Random {
// `DefaultRNG`s initializer defaults to `internal`.
public struct DefaultRNG: RandomNumberGenerator {
public func next() -> UInt64 { ... }
...etc...
}
public var defaultRng = DefaultRNG() // initializer is visible here.
}
var otherRng = Random.DefaultRNG() // intializer is not visible here, so compilation fails.
Future Directions
Allowing namespaces to be declared inside of generic types. This probably depends on whether and how we decide to handle static stored declarations in generic types. We could always just require that namespaces inside generic types only have computed properties.
Allowing one to import a public declaration from a namespace (or all of the public declarations) directly into the current module. Individual declaration import would also be nice to have in general.
Sugar for placing the contents of an entire file inside of a namespace. This is purely additive.
Alternatives Considered
Do nothing.
Allow prefixing enum declarations with static to make all enclosed non-type declarations static, which would solve only the boilerplate issue.
Here's another issue with enums-as-namespaces that should be mentioned: the enum is still a type, and so it generates type metadata in the binary that must be unique or else you end up with duplicate symbols at link time.
This means that if you spread out the definitions in a namespace across multiple files, you may only have a single enum Foo declaration, and the rest are extension Foo. It's asymmetrical and awkward when all you really want is for the declarations to be grouped together and have their names mangled in the same way in the final binary.
It's easy to imagine code generation scenarios where you might want to piece together a number of related declarations across multiple files into the same namespace, and the restriction above forces the generator to have to ensure that there's only a single instance of the actual type declaration instead of just being able to independently produce the namespaced symbols.
That's a good point. That probably makes it worthwhile to just use namespace Foo everywhere that a namespace is used instead of allowing extension Foo.
@taylorsift I feel like your idea and my idea are very close. At the very least the func Namespace.foo() { } syntax is trivial sugar over my proposal, and even if we did base the implementation on enum internally we'd still want something akin to your namespace = Foo syntax. I'm not sure I like the namespace Foo(import Bar) syntax though.
I'm reviving this thread because the enum as namespace thing bugs me every time, and I think this proposal covers the issue really well. There are other threads on this topic too, and AFAICT the arguments against mostly boil down to "let's avoid adding more keywords". But I think the differences between enum and the proposed namespace are valuable.
There may be cases where an uninhabited enum makes sense as a return type (like Never, of course), but not for one that's supposed to be a namespace, and I want to be able to express that.
As proposed, a namespace is mostly a more restricted version of an enum, except for being able to include protocols. If this were to be implemented, what are the potential complications of allowing nested protocols?
..or at least, I thought I was reviving the thread
I'll try again because I really am curious about what the answers might be. As I said, the counter-arguments I've seen tend to be essentially that it's not worth adding another keyword. IMO this thread has some decent arguments that it is worthwhile, so I'd like to see other angles addressed as well.
Can anyone comment on whether namespaces, especially as proposed here, might be hard to implement? Or why they might otherwise be actively bad?
I use a lot of nesting, since it makes it really nice on the calling side where each context level is its own namespace, ex calling Layout.Image.margin inside of MyView... and you can use the same identifiers, just in different contexts, ex calling Style.background or Style.Image.background
After browsing different discussions in these forums about submodules and namespaces, I don't think the idea of a submodule really satisfies my use case... from my understanding of the Namespaces Ă— submodules proposal, submodules can be nested within each other, sure... but they can't be nested within types... and I don't want to have to define a MyViewConstants submodule outside of MyView just to be able to define constants, for these reasons:
Naming conflicts
MyViewConstants should never be more visible than MyView -- which could happen if they were both top level types
What I would like to see is a namespace type that is non-instantiable, non-returnable, non-extendable, can be nested inside of other types, and where you don't need to mark static on everything... purely for the intention of better organization and code readability.
imo that outweighs the argument for not cluttering Swift with more keywords. I've also seen an argument in the forums that caseless enums are an established pattern that people are used to, but in my experience it's usually a pretty opinionated debate about struct with a private initializer vs caseless enum... since it's considered an anti-pattern to what an enum should really be.
that said, i just don’t see much utility in statically guaranteeing that an enum is empty. i don't think i've ever accidentally added a case to a namespace enumeration.
Namespacing within a module only becomes necessary if your module is large.
Why not decompose your large modules into smaller modules? Once you reach a point where your namespacing needs are addressed through module decomposition, you may find that you now actually have modules that "do one thing and do it well", that your project is more idiomatic, modular, and maintainable, and that you've benefitted in areas you weren't even expecting.
Free namespacing through the existing modules-as-namespaces paradigm will just be the cherry on top.
This come at a price, it is not for free. Separating into small modules need knowledge, expertise and time. Projects can have a lack of one or all of this.
The second point is about api design. Even if all your modules are small, well formed and designed, you can get ergonomic benefits in cases like UITableView.Delegate instead of having UITableView class and UITableViewDelegate protocol.
NameSpaces help better organize code even in small modules.
Also, at present, Swift modules all share the same single-level namespace. This doesn't scale well at all for many large codebases, which is why Bazel for example defaults to a path_based_module_naming approach. It's consistent (vs. everyone in a shared monorepo choosing names for every one of their modules, which would be horribly unpredictable), but it's also an ugly wart on our Swift usage.
Submodules have been discussed off-and-on, but (if based on the Clang definition) they alone wouldn't solve that problem because submodules aren't just naming hierarchies like Java packages are; some parent module fully contains its submodules and are imported as such.
Even if that weren't the case, there are some limited situations where a namespace that can span modules would be useful. SwiftProtobuf also does a Package_Based_Name_Mangling for generated types because protobuf's concept of "packages" doesn't map cleanly to Swift modules. It would be nice to be able to say that module M1 and module M2 can both define namespaces that are effectively merged if a client imports both M1 and M2. But this might be too niche of a case to rationalize.
I believe the increasing number of uninhabited enums being used in the standard library to namespace things like atomics and unicode are sufficient proof that we need some form of namespacing in Swift. Just organize your code better isn't a very compelling argument against namespaces.
The problem is implementing them. Until someone can spare the time and effort to implement proper namespaces for Swift, there's not going to be any movement on this.
this is not a niche use case to me, i use this pattern extensively. but i do not see the motivation for new language features here. can’t we already do this by selectively re-@_exporteding the enum namespaces?
I'm not sure I follow. If you have the following independent modules:
// GeneratedModule1
public enum Com {
public enum MyCompany {
public struct MyType1 {}
}
}
// GeneratedModule2
public enum Com {
public enum MyCompany {
public struct MyType2 {}
}
}
What are you proposing that avoids this error, if the goal is to avoid requiring module-qualifying the names?
import GeneratedModule1
import GeneratedModule2
let x: Com.MyCompany.MyType1 // error: `Com` is ambiguous
let y: Com.MyCompany.MyType2 // ditto
Footnote: Interestingly, function call resolution collects candidates differently, so the following does work without error:
import GeneratedModule1
import GeneratedModule2
let x = Com.MyCompany.MyType1()
let y = Com.MyCompany.MyType2()
But any attempt to use them in type position will fail without module qualification. I'm not sure if that inconsistency is desired behavior, though?
What happens when both those modules then declare their own MyCompany in extensions of Com, and a client imports both and tries to reference one of them? Seems like the same problem all the way down.
This doesn't feel like it scales any better than the original approach. The goal is to avoid needing a single canonical definition of the namespace anywhere, because—especially for code generation—you don't always have a full view of the world and you can't guarantee that you'll create exactly one of those things.
you would do the same thing: create a single-type module that imports _Com and defines the MyCompany namespace, and libraries that use the two-level namespace would reexport that namespace instead.
i’m not seeing the motivation for using macros to define the namespaces themselves, in my mind namespaces are so large and few in number that the macros should be sticking to populating the namespaces with extensions.
i suppose if you have a very high number of namespaces that you want to generate with a macro, then that should be a separate tool that has the sole responsibility for creating the canonical namespace definitions.
I'm not referring to macros; by "code generation" I mean a build system generating Swift code from some other external input, like protobufs.
This by your own definition requires some task in the build system to have a full view of the world, which is precisely what needs to be avoided. Such tasks become bottlenecks during build planning because introducing a new namespace anywhere in the build graph requires the whole set to be recomputed.