i wrote up the following intending for it to be a starting point for discussing a new language feature, but as i was finishing it i realized it was really more of an argument for why this proposal should pass.
in C++, i always spent (wasted?) a lot of time thinking about memory safety.
in swift i find that i spend (waste?) a lot of time thinking about lexical hierarchy.
here’s an example of a hypothetical library product that vends one single overwhelmingly important type, and a supporting protocol to be used with that type:
struct Barbie { enum ID {} struct Instant {} struct Camera<Film> where Film:BarbieFilm { struct Photo {} } ... } protocol BarbieFilm { var id:Barbie.ID { get } var timestamp:Barbie.Instant { get } }i say product and not module because putting both of these definitions in the same module precludes using Barbie as a module name. and in light of that i think it’s helpful to list what i think are some reasonable goals regarding the usability of a library product:
a module’s name should be a description of what the module contains, and not a description of the fact that it is a module. so we really want to use this library as
import Barbieand notimport BarbieFramework.library consumers should standardize on one
importspelling. in other words, if there is a module namedBarbie, but most of the library’s critical functionality is actually@_exportedfrom an umbrella module likeBarbieFramework, andimport BarbieFrameworkis the prescribed way to consume the library, we do not want people to typeimport Barbieand then wonder why nothing is working.this is also a way of restating goal #1, if the “public-facing” umbrella module has the simplest name amongst its constituent targets, no one will accidentally import a component of the library when they mean to import the whole thing. (think
NIOvsNIOCore.)library API should obey namespace shadowing rules. in swift, this means if you have a module named
Barbieand it also has a top-level type namedBarbie, then every other (public) type vended by that module must be lexically scoped toBarbie. things that cannot be nested (like protocols) need to be@_exportedfrom another module with a different name.library consumers should never utter an internal module name. this is just another way of saying that library consumers should not have to care about a library’s internal dependency graph, and they should not care what module a type is “really defined in”.
at the pure source code level, this is an equivalent statement to “your library should have a very low probability of name collisions with other code”, because if you never experience a name collision, then you never have to qualify a reference with a module name in the first place.
and yet “what module a type is actually defined in” is kind of unescapable, it shows up in URLs, it shows up in symbol links, and people get it wrong all the time. so even if you have the most well-designed lexical structure possible, a naming scheme so holy that it will never collide with anyone else’s names, you still have to care about what your inner modules are named, you can’t just prefix them all with underscores.
anyway, i was originally envisioning some kind of “public-facing module name feature”, but then i realized these “usability principles” are really just an elaborate mitigation strategy for the problem described in goal #3: if it weren’t for the fact that we need to reexport protocols from hidden modules with @_exported because they cannot live in the the user-facing module, then we don’t need to lower the rest of the module’s implementation into a hidden sibling module, in fact we can really dispense with the whole “hidden modules” nonsense entirely.
i for one can’t wait for the day where i can stop obsessing about module naming because i will finally be able to declare a protocol in the same module as the code that uses it.