Namespaces × submodules

Really? Do you consider namespaces and submodules to be the same thing? Because this thread is about namespaces, which I consider less to do with access control and more to do with code structure. People want a way to nest types and functionality in a more granular way than just a top level flat module. And a lot of people want a language level feature to accomplish this rather than nesting it all inside of a degenerate enum.

5 Likes

Namespaces are a form of controlling access to code.

Unpopular opinion I guess :slight_smile:

They don't have to be but I think it would be really odd if they weren't. I suppose it could help only in reducing the number of types exposed at the top level of a module, but that really only matters for auto-complete when you're writing code. Not enough of a reason to add an otherwise-unfunctional addition to the language.

Maybe I'm missing something. How could private in a submodule ever mean public or internal? How could fileprivate have access across Modules?

AFAIK, all that has been proposed/discussed with regard to submodules relates to how code within a single Module can be further subdivided in ways that are less granular than individual files, while allowing the same access control rules that govern Swift currently to apply consistently to that new conceptual boundary.

I'd disagree with this. My understanding of namespaces is as a form of deconfliction, not access control. Namespaces and access control are orthogonal concepts. In other words, a namespace of A or B allows you to say: "There's a Foo in domain A and there's a Foo in domain B, and A.Foo is not the same Foo as B.Foo."

I gather that @taylorswift 's initial point is that there's some potential common ground between the fundamental concepts of namespaces and submodules since the both generally revolve around the concept of "This code is all relative to a particular domain". If you can describe a domain of logic A that is distinct from a domain of logic B, then it makes sense to be able to:

  • Speak of types being part of A or B (or neither) domains, and disambiguate Foos in each domain that would otherwise be spelled the same (namespaces)
  • Let A hide some of its details from other code, let B hide some of its details from other code (submodules)

And "Hey, wouldn't it be good if we can solve both parts of this problem with one feature."

10 Likes

See this thread:

This explanation is the best I've read so far.

I'm going to use the term submodule here, but I'm referring to @taylorswift's combined namespace/submodule concept in general.

If we assume the following as a straw-man position to get the discussion flowing:

  1. No material changes will be allowed to existing open/public/internal/fileprivate/private spectrum
  2. A definition of "submodule" which includes the introduction of a visibility and organizational boundary between the file level and the module level

Then it follows that we need to discuss how the existing access modifiers apply to this new boundary.

For the sake of argument, I'll posit that if the "submodule" boundary is functionally less granular than a file, then a single file cannot contain declarations in more than one submodule (ie: file-splitting).

This is a position that does introduce some important differentiation between traditional namespace and submodules concepts. There's not much of a reason to assert "no file-splitting" for a pure namespace, but it might be important for submodules to prevent fileprivate from being able to artificially bridge submodules (although, having said that... that might be a feature), so for now, let's assume we don't want that. And this is, I think, what @masters3d was warning about.

If we assert no "file splitting" (for now) then we can safely dismiss private and fileprivate. They will not change whether they are used within a submodule or not. I believe this is good. We want as little to change about existing access control as possible to ensure we can continue to reason about code.

This leaves internal, public, and open to understand. For the purposes of this discussion, I'll refer to all three of these conditions as public as in "is it public or not?" Does the submodule boundary impact the meaning of "public", or not?

I'm of two minds on this: On the one hand we need a way to distinguish that boundary. On the other hand we don't want to introduce new meanings of "public" around that boundary.

Does a submodule behave the same as a Module boundary for the purposes of the "public" spectrum (that is: Does internal or public refer to visibility outside the submodule, and within the parent module)? If so, then we need a way for the module to say "no really, submodule B is not just visible to me, but actually public", perhaps something like export B (which then allows re-namespacing like export B as Bar which could be interesting).

If a submodule doesn't behave the same as Module boundary for the purposes of the "public" spectrum (that is, public struct B.Foo {} is already visible outside the Module because of public), then how can we declare members of the submodule which live in different files, that know about each other, but aren't visible outside the submodule?

Thoughts?

1 Like
  1. What would be the implicit access level inside submodules?

  2. I understand that swift’s internal is like Java’s public but this is only true when there is only one module. How is internal public inside submodules?

If I'm not misunderstanding, what you are describing is very similar to the way (I think, I've only dabbled in Rust a bit) the Rust module system works (sorry if this has already been brought up, I haven't read the whole thread). Things are either pub (public) or not. If they are, they can be accessed from outside the module. Modules can pub use their submodules or members of their submodules, which makes them accessible from the outside.

I think a system like that could fit pretty well into Swift, if we extend the semantics of public/open, private and internal to always describe visibility outside the current module, including parent modules.

An idea I have had in this direction is:
open: subclass-able anywhere
public: usable anywhere
external: (new) subclass-able in the current module
???: (new) usable in the current module
internal: subclass-able in the current sub-module

This is still source compatible with the current system, because the current system, interpreted in this system, is simply that modules would previously (currently) simply be limited to 1 sub-module per module, so we have only ever had the appearance of sub-modules.

Additionally, I am currently unsure of which of what I have listed as external and ??? are more deserving of being called "external", as I do not have a name for ??? yet, and I do not know which I think is more likely to be used.

okay okay, let’s keep a few things straight,, submodules do not and should not have anything to do with symbol visibility. That’s the difference between a submodule and a module. Submodules are purely meant to be an organizational tool for the programmer. They should be completely transparent to the compiler. So submodules can have access control (if we want them to!), but no visibility barriers.

1 Like
  1. the term “submodule boundary” makes no sense, if it has a boundary, then it’s a module.
  2. something to worry about with public submodules: since submodules are namespaces (hence the name of this thread!!), if a submodule is public, then the name of the submodule is part of your library API (and ABI). This is true because even though the submodule is transparent, it affects the qualified names of all of its members (this is true for all namespaces too.) I don’t think this is too different from the considerations involving all other Swift declarations, so I don’t think we’re “introducing new meanings of ‘public’” here.
  3. I don’t get what distinction you’re trying to draw here. if something is public, then it’s exported. That’s what “public” means. code in the same module doesn’t care (from an access control standpoint) whether a symbol is public or not. Public only matters to external code.

we can’t do this. if a submodule has a public member, then the submodule declaration too should be public, since its name is part of the library interface. It it wasn’t, then they would provide no namespacing and there would be no point in having them.

2 Likes

Here’s how I see things:

• A submodule is not a module. In particular, un-annotated declarations are implicitly internal, and thus visible to the entire module, regardless of which (if any) submodule they are in. There needs to be a separate keyword for “visible in this submodule only”, and I recommend we find one that can completely replace fileprivate, using the rationale that a file is inherently its own submodule unless specifically made part of another submodule.

• A submodule is not the same as a namespace. Namespaces are a separate concept, similar to an empty enum. A single file can be used for multiple namespaces, and a single namespace can be used in multiple files. By contrast, a submodule is specifically used for grouping related files.

• If a submodule behaves like a namespace, it should be a “transparent” namespace, meaning that its name *does not* need to be provided when accessing things inside it (except when necessary for disambiguation).

• Submodules cannot be nested. A module can contain submodules, and a submodule can contain files, and that is that.

• • •

Thus, a submodules is primarily an organizational tool, providing a level of access control intermediate between “one file” and “the whole module”. Anything inside the submodule can be accessed directly, without writing the submodule name, from anywhere that it is visible.

1 Like
  • Just for clarification, by that you don‘t mean that a submodule cannot depend on a different sub-/module?

  • What if I want to break my sub-module into even smaller parts and those parts should be encapsulated from the main module and even partly from the module itself? If we can already do module A imports module B which imports but not expose module C then why not allow module A has submodule B which has a nested own submodule C and D?

1 Like