Namespaces × submodules

As is well known,, i am not the biggest fan of adding the namespace keyword to the language solely so we can have namespaces in Swift. however! I am a big proponent of submodules and I just had an idea of how we can combine the two concepts and get two features with one keyword.

Right now, Swift has modules which have the concept of binary compatibility and distribution, and are equivalent to what other languages call packages. Modules are great for distributing full libraries and applications. However, they’re terrible as a tool for code organization. anyone who’s tried separating out all the low-level math code from their project into a separate module and seen performance fall off a cliff due to the new binary boundary knows this. What we really want is a way to group related code into submodules in a way that’s transparent to the compiler.

This sounds a lot like namespacing. What if namespaces could also be used as submodules? Here’s how i think it could work:

We declare a namespace with the keyword namespace, which can be prefixed with an access modifier like any actual Swift type.

public namespace Math

we place stuff in the namespace by prefixing its declared name with the namespace, separated by a dot

public struct Math.Vector {}

namespaces can be nested within each other too.

public namespace Math.Matrix 

public struct Math.Matrix.Mat4 {}

just like everyone would expect, things inside the namespace don’t need the namespace qualifier to reference symbols in the same namespace

public func Math.Matrix.multiply(_ A:Mat4, _ B:Mat4) -> Mat4 
{
    ...
}

since it would be tedious to prefix all your types like that if you want to make a submodule, which likely includes an entire folder of .swift files, we can use = to specify a default namespace that all top-level declarations in the same file will automatically be placed under. (This is not using namespace blah; in C++, whose function is to resolve namespaces, not put things inside them.)

// matrix.swift
namespace = Math.Matrix 

public func multiply(_ A:Mat4, _ B:Mat4) -> Mat4 
{
    ...
}

this actually has the interesting side feature of allowing for an elegant syntax for rebinding namespace names within a file scope (equivalent to import as in Python). if this looks a lot like the syntax for typealiases, that’s because it does :blush:

namespace Matrix = Math.Matrix

public func Matrix.multiply(_ A:Mat4, _ B:Mat4) -> Mat4 
{
    ...
}

By default, all internal and above namespaces are visible to all code within the same module. To encapsulate certain namespaces so that they must be explicitly imported to be used, we can add the final modifier to their declaration. This creates a closed namespace, or a submodule.

final namespace MySubmodule

By default, things inside a submodule namespace can only reference symbols in the same submodule. Code that does not live inside any submodule can’t access symbols inside any submodule. (Although you can obviously define members of a submodule from anywhere.) To import things from another submodule, you use the import keyword. You can import symbols at the scope of any namespace.

namespace Math.Matrix(import Submodule1, import Submodule2.Bar)

you can import symbols into the top level like this

namespace(import MySubmodule.Foo)
14 Likes

Considering the many discussions about namespaces in swift over the years, I'm surprised that this hasn't gained more attention.

In general, syntax wise, I quite like it. But saying that, I'm not sure around the proposed import syntax yet, it's a bit funky and very un-swift like. Something like this would allow us to stay away from 'the tree of doom' where everything gets encapsulated in a visual indented scope.

I do wonder how feasible the submodule, but not a binary separated submodule, actually is. This would need some intervention from someone with a little more knowhow around this area.

As someone who has had a personal project die on the vine thanks to this exact effect (and getting so frustrated with it that I lost interest [for now]), I wholeheartedly will support any submodules approach that will come close to solving the problem at this point. I'd also be happy to contribute to whatever degree I can be useful.

Having said that, I'm not thrilled with the proposed syntax, but we can refine those details as we work through it. I would like to see us take the time to look through the various different approaches to submodules that have been discussed, and see if we can find the best solution, as opposed to the least objectionable one... but if the least objectionable one is the only one we can get... I can live with it :)

I'm beyond excited with the sudden resurgence of submodules threads, but I'm a little cautious... Is there any indication that there's will to solve this problem at this time?

From my perspective, I can appreciate the usefulness of namespaces, but my number one need from a submodules solution is providing an organizational and visibility boundary between the level of Module and file. Anything more than that is gravy.

5 Likes

When the namespace = Foo declaration is used, there should be some way to exclude declarations within the file. Perhaps prefixing an explicit namespace would override the file's default?

I can't think of a good reason to have a top-level declaration in the same file if it's not meant to be in the namespace, but I think it should be possible.

I am not a fan of filesystem-based hierarchy (too many Java-induced nightmares), but this seems reasonable to me. It has the benefit of making it crystal clear what is in the submodule/namespace, and what is out.

2 Likes

I wrote this proposal exactly to avoid file-system-based abstractions since people seem to be so opposed to that on here. personally I could go either way but the intent here is to provide an alternative to directory-centric module organization

3 Likes

Coming from C#, I'm used to namespaces using curly braces to contain the sourcecode inside them.

It does introduce extra indentation, but for the clarity and ease of use it provides, I'd say it might be worth it.

Here's the documentation on those: namespace (C# reference)

How's that kind of syntax sound?

1 Like

I don't really like the extra indentation, since the namespace isn't likely to have any content of its own, though conceivably the convention could become to not indent the contents of the namespace.

I do really like the public namespace Math approach because it composes nicely with things like access control, etc within the Swift idiom. It feels right and discoverable, and extends nicely into potential future directions.

Things I don't really like in this are:

There's going to be a tendency to drop namespace declarations into the top of the first file to declare a type in a namespace, and over time, that file is going to become the "magic header" of the namespace even if it evolves to be not the most important file in the namespace. This is the shortcoming of a "simple declaration" namespace over something more structured... it relies entirely on convention to keep track of where the namespaces get declared. Maybe that's not the worst thing in the world, but it's worth considering alternatives.

Type adoption into a namespace using struct Math.Matrix feels a little too intrusive to me, but I'm not in love with any alternatives I can think of right now:

  • @namespace(Math) public struct Matrix - misuse of attributes, I think.
  • in Math public struct Matrix - clear but re-uses in keyword

I also find the proposed use of final to be a little odd. It doesn't feel like an obvious or discoverable usage, and I feel like I'd always have to look up what it means when applied to a namespace. Why would final affect visibility?

By default, things inside a submodule namespace can only reference symbols in the same submodule.
Code that does not live inside any submodule can’t access symbols inside any submodule.

How can code within a submodule access code in the broader module which is not in a submodule? Ie:

Module A // A cannot reference B or C without importing
     |
     +--- Submodule B // How can we reference A from here?
     +--- Submodule C // C cannot reference B without importing
2 Likes

Y’all keep in mind with any namespacing scheme, you’re gonna have to declare the namespace somewhere. This is true even for the curly-braces namespace Foo {} scheme — the difference being that the braces makes it so that everything in a namespace has to be defined in the same file.

Arguing “there’s no specific place to put the namespace declaration” is as silly as arguing “there’s no specific place to put a protocol declaration”. You have to define the protocol somewhere and all the extensions can live in other places.

1 Like

I think I'd prefer reusing the namespace resolution of the existing type system here: things in namespace A can access other things in namespace A without qualification, and can access things from the enclosing module without qualification if there's no conflicting definitions and with qualification otherwise.

So, for your graph:

Module A // A can access anything in B or C by either import or qualified access (i.e. B.foo)
     |
     +--- Submodule B // Reference A directly if unambiguous or by qualified access.
     +--- Submodule C // Reference members of B as B.foo or, if ambiguous, A.B.foo, or through import.

As for specifying that a top level declaration is not inside the stated namespace, you can just prefix it with the module name:

in public namespace Math

public let pi = 3.14159 // This is in Math
public func Module.foo() { // This is in the enclosing module
    print("Eat some delicious \(pi)")
}

Also, even in the curly braces system, there's no need to have a single declaration of the namespace, you could allow multiple declarations then either:

  • Require that each declaration has the same access specifier.
  • Treat the given access specifier as merely an upper bound on the declarations in that scope, then give the namespace itself the least restrictive qualifier given to it in the whole module.

I’m really against introducing more indentation into the language since Swift favors readability over brevity so much you get really uncomfortably long lines a lot

1 Like

I don’t think the alternatives are any better. People have suggested

  • Namespaces.swift” files
    Problematic because you’d basically have a bunch of tiny 5 or 6 line files that exist for the sake of existing, and you have the problem of where to put the declaration files themselves. Also we’d have to settle on a convention of the file name and we all know where that leads. Note that we can’t follow the SPM route of using a Package.swift file because namespaces would exist at the language level, below the SPM. So it should be possible to use namespaces with just a bare swiftc file1.swift file2.swift ... invocation.

  • directory-based namespacing
    Will always be opposed by some because it’s based on the file system, and also has the problem where you may want to place some swift files in a folder but not create a new namespace.

3 Likes

What about requiring the folder to have an extension? E.g. files in Math.swiftsubmodule would be in the Math module.

1 Like

that would be really weird

No no, not really. You can use that syntax in C# to define things inside the same namespace from different files. If the same namespace is being "declared" in different files, all of the items inside those "declarations" belong to that namespace.

Well that's a good point. Hmm. I dunno then. It's a really good solution IMO, except for the indentation.

Maybe we could use a similar syntax that doesn't introduce indentation? That might be too invisible though. But if we were going to try it, maybe something like this could work?

#namespace Foo

internal protocol Bar {
    var Baz : Int
}

#/namespace

so how do you define access control on them? if you want to change a namespace from internal to public, do you have to change it everywhere?

i mean # has a really specific meaning in swift it defines stuff that gets replaced at compile time, like the “swift preprocessor”

Namespaces have no access control. Or rather, they are all public by default (and for good). Items inside them though, can be marked public or internal (internal is the default) and like so you achieve what you want (make the internal items be accessible only from inside the namespace)

Yeah, I know... Maybe with a new special character? But the bar for new special characters is kinda high.