Wonderful comments. I really enjoy your take on submodules which keeps most of 
the power while keeping the simplicity. Comments below:


Sent from my iPhone

On 1 Mar 2017, at 07:55, Brent Royal-Gordon via swift-evolution 
<swift-evolution@swift.org> wrote:

>> On Feb 24, 2017, at 11:34 AM, Matthew Johnson via swift-evolution 
>> <swift-evolution@swift.org> wrote:
>> 
>> Scope-based submodules
>> 
>>    • Proposal: SE-NNNN
>>    • Authors: Matthew Johnson
>>    • Review Manager: TBD
>>    • Status: Awaiting review
> 
> Well, this is certainly comprehensive! Sorry about the delay in answering; 
> I've been hosting a house guest and haven't had a lot of free time.
> 
>> The primary goal of this proposal are to introduce a unit of encapsulation 
>> within a module that is larger than a file as a means of adding explicit 
>> structure to a large program. All other goals are subordinate to this goal 
>> and should be considered in light of it. 
> 
> I agree with this as the primary goal of a submodule system.
> 
>> Some other goals of this proposal are:
>> 
>>    • Submodules should help us to manage and understand the internal 
>> dependencies of a large, complex system.
>>    • Submodules should be able to collaborate with peer submodules without 
>> necessarily being exposed to the rest of the module.
>>    • A module should not be required to expose its internal submodule 
>> structure to users when symbols are exported.
>>    • It should be possible to extract a submodule from existing code with 
>> minimal friction. The only difficulty should be breaking any circular 
>> dependencies.
> 
> One goal I don't see mentioned here is "segment the API surface exposed to 
> importing code". The `UIGestureRecognizerSubclass` use case has been 
> thoroughly discussed, but I think there are probably a lot of cases where 
> there are two "sides" to an API and it'd often be helpful to hide one unless 
> it's needed. `URLProtocol` and `URLProtocolClient` come to mind; the many 
> weird little classes and symbols related to `NSAtomicStore` and 
> `NSIncrementalStore` might be another.
> 
> (I'm not necessarily suggesting that the Foundation and Core Data overlays 
> should move these into submodules—I'm suggesting that, if they were 
> implemented in a Swift with a submodule feature, they would be candidates for 
> submodule encapsulation.)
> 
>> Submodule names form a hierarchical path:
>> 
>>    • The fully qualified name of the submodule specified by 
>> Submodule.InnerSubmodule is: MyModuleName.Submodule.InnerSubmodule.
>>    • In this example, InnerSubmodule is a child of Submodule.
>>    • A submodule may not have the same name as any of its ancestors. This 
>> follows the rule used by types.
> 
> Does being in a nested submodule have any semantic effect, or is it just a 
> naming trick?
> 
>> Submodules may not be extended. They form strictly nested scopes.
>> 
>>    • The only way to place code in a submodule is with a submodule 
>> declaration at the top of a file.
>>    • All code in a file exists in a single submodule.
> 
> I'm a big supporter of the 1-to-N submodule-to-file approach.

Agreed.

>> There are several other ways to specify which submodule the top-level scope 
>> of a file is in. All of these alternatives share a crucial problem: you 
>> can’t tell what submodule your code is in by looking at the file. 
>> 
>> The alternatives are:
>> 
>>    • Use a manifest file. This would be painful to maintain.
>>    • Use file system paths. This is too tightly coupled to physical 
>> organization. Appendix A discusses file system independence in more detail.
>>    • Leave this up to the build system. This makes it more difficult for a 
>> module to support multiple build systems.
> 
> I'm going to push back on this a little. I don't like the top-of-file 
> `submodule` declaration for several reasons:
> 
>    1. Declarations in a Swift file are almost always order-independent. There 
> certainly aren't any that must be the first thing in the file to be valid.
> 
>    2. Swift usually keeps configuration stuff out of source files so you can 
> copy and paste snippets of code or whole files around with minimum fuss. 
> Putting `submodule` declarations in files means that developers would need to 
> open and modify those files if they wanted to copy them to a different 
> project. (It's worth noting that your own goal of making it easy to extract 
> submodules into separate modules is undermined by submodule declarations 
> inside files.)
> 
>    3. However you're organizing your source code—whether in the file system, 
> an IDE project, or whatever else—it's very likely that you will end up 
> organizing files by submodule. That means either information about submodules 
> will have to be specified twice—once in a canonical declaration and again in 
> source file organization—and kept in sync, or IDEs and tooling will have to 
> interpret the `submodule` declarations in source files and reflect that 
> information in their UIs.
> 
>    4. Your cited reason for rejecting build system-based approaches is that 
> "This makes it more difficult for a module to support multiple build 
> systems", but Swift has this same problem in *many* other parts of its 
> design. For instance, module names and dependencies are build system 
> concerns, despite the fact that this makes it harder to support multiple 
> build systems. I can only conclude that supporting multiple build systems 
> with a single code base is, in the long term, a non-goal, presumably by 
> improving the Xcode/SwiftPM story in some way.
> 
> I'm still a fan of build-system-based approaches because I think they're 
> better about these issues. The only way that they're worse is that—as you 
> note—it may not be clear which submodule a particular file is in. But I think 
> this is basically a UI problem for editors.

I'm also a big fan of the build-system or file-system approach, for exactly the 
same reasons. The copy/pasting argument is important IMHO. For example, the 
suggestion which has been discussed a lot on here to make private change to 
submodule scope when inside a submodule would have the same negative effect on 
the ability to copy paste code between a submodule and top-module.

>> Top-level export
>> 
>> All export statements consist of an access modifier, the export keyword, and 
>> a submodule name:
>> 
>> open export ChildSubmodule
> 
> Is the access control keyword mandatory?
> 
> If we do indeed use `submodule` statements, could we attach the attributes to 
> them, rather than having a separate statement in a different file?
> 
> What does it mean if a `public` or `open` symbol is in a submodule which is 
> not `export`ed?
> 
>>    • A submodule may be published under a different external name using the 
>> export as NewName syntax*.
> 
> What's the use case for this feature?
> 
>>    • @implicit causes symbols from the submodule to be implicitly imported 
>> when the module is imported.
>>    • @inline causes the symbols from the submodule to appear as if they had 
>> been declared directly within the top-level submodule.
> 
> So if you write `@implicit public export Bar` in module `Foo`, then writing 
> `import Foo` also imports `Foo.Bar.Baz` *as* `Foo.Bar.Baz`, whereas `@inline 
> public export Bar` copies `Foo.Bar.Baz` into `Foo`, so it imports as 
> `Foo.Baz`?
> 
> What's the use case for supporting both of these behaviors?
> 
>> Exports within the module
>> 
>> A submodule may bound the maximum visibility of any of its descendent 
>> submodules by explicitly exporting it:
> 
> I'm not sure how valuable this feature is in this kind of submodule design.
> 
> ***
> 
> To avoid being coy, here's the export control model that *I* think would make 
> the most sense for this general class of submodule system designs:
> 
> 1. A submodule with `public` or `open` symbols is importable from outside the 
> module. There is no need to separately mark the submodule as importable.

I had not thought of that but it's very elegant.

> 2. Normally, references within the module to submodule symbols need to be 
> prefixed with the submodule name. (That is, in top-level `Foo` code, you need 
> to write `Bar.Baz` to access `Foo.Bar.Baz`). As a convenience, you can import 
> a submodule, which makes the submodule's symbols available to that file as 
> though they were top-level module symbols.
> 
> 3. When you import a submodule, you can mark it with `@exported`; this 
> indicates that the symbols in that submodule should be aliased and, if 
> `public` or `open`, re-exported to other modules.

Could you explain this in more detail?

> 4. There are no special facilities for renaming submodules or implicitly 
> importing submodules.
> 
>> Importing submodules
>> 
>> Submodules are imported in exactly the same way as an external module by 
>> using an import statement.
> 
> Okay, but what exactly does importing *do*? Set up un-prefixed private 
> aliases for the submodule's internal-and-up APIs?
> 
>> There are a few additional details that are not applicable for external 
>> modules:
>> 
>>    • Circular imports are not allowed.
> 
> Why not? In this design, all submodules are evaluated at once, so I'm not 
> sure why circular imports would be a problem.
> 
>> // `Grandparent` and all of its descendents can see `Child1` (fully 
>> qualified: `Grandparent.Parent.Child1`)
>> // This reads: `Child1` is scoped to `Grandparent`.
>> 
>> scoped(Grandparent) export Child1
>> 
>> // `Child2` is visible throughout the module but may not be exported for use 
>> by clients.
>> // This reads: `Child2` is scoped to the module.
>> 
>> scoped(module) export Child2
>> 
>> With parameterization, scoped has the power to specify all access levels 
>> that Swift has today:
>> 
>> `scoped`                                      == `private` (Swift 3)
>> `scoped(file)`                                == `private` (Swift 2 & 4?) == 
>> `fileprivate` (Swift 3)
>> `scoped(submodule)`                           == `internal`
>> `scoped(public) scoped(internal, inherit)`*   == `public`
>> `scoped(public)`                              == `open`
>> 
>> The parameterization of scoped also allows us to reference other scopes that 
>> we cannot in today’s system, specifically extensions: scoped(extension) and 
>> outer types: scoped(TypeName).
> 
> What is the purpose of creating more verbose aliases for existing access 
> levels? I can't think of one, which means that these are redundant.
> 
> And if we remove them as redundant, the remaining access control levels look 
> like:
> 
>    scoped
>    private
>    scoped(TypeName)
>    internal
>    scoped(SomeModule)
>    scoped(module)
>    scoped(extension)
>    public
>    open
> 
> There's just no logic to the use of the `scoped` keyword here—it doesn't 
> really mean anything other than "we didn't want to assign a keyword to this 
> access level".
> 
> ***
> 
> I think we need to go back to first principles here. The reason to introduce 
> a new access level is that we believe that a submodule is a large enough unit 
> of code that it will simultaneously need to encapsulate some of its 
> implementation details from other submodules, *and* have some of its own 
> implementation details encapsulated from the rest of the submodule.

That's where I disagree. Why would Swift need that level of differentiation and 
not C# or Java, which have had packages and namespaces without it.

> Thus, we need at least three access levels within a submodule: one that 
> exposes an API to other submodules, one that exposes an API throughout a 
> submodule, and one that exposes it to only part of a submodule.
> 
> What we do *not* need is a way to allow access only from certain other named 
> submodules. The goal is to separate external and internal interfaces, not to 
> micromanage who can access what.
> 
> Basically, that means we need one of two things. Keeping all existing 
> keywords the same—i.e., not removing either `private` or `fileprivate`— and 
> using `semi-` as a placeholder, we want to either have:
> 
>    private: surrounding scope
>    fileprivate: surrounding file
>    semi-internal: surrounding submodule
>    internal: surrounding module
>    public: all modules (no subclassing)
>    open: all modules (with subclassing)
> 
> Or:
> 
>    private: surrounding scope
>    fileprivate: surrounding file
>    internal: surrounding submodule
>    semi-public: surrounding module
>    public: all modules (no subclassing)
>    open: all modules (with subclassing)
> 
> The difference between the two is that, with `semi-internal` below 
> `internal`, submodule APIs are exposed by default to other submodules; with 
> `semi-public` above `internal`, submodule APIs are encapsulated by default 
> from other submodules.
> 
> I think encapsulating by default is the right decision, so we want the 
> `semi-public` design. But there's also a second reason to use that design: We 
> can anticipate another use case for it. The library resilience design 
> document discusses the idea of "resilience domains"—groups of libraries whose 
> versions are always matched, and which therefore don't need to use resilient 
> representations of each others' data structures—and the idea of having 
> "SPIs", basically APIs that are only public to certain clients. I think these 
> ideas could be conflated, so that a semi-public API would be available both 
> to other submodules in the module and to other libraries in your resilience 
> domain, and that this feature could be used to expose SPIs.
> 
> So, that leaves an important question: what the hell do you call this thing? 
> My best suggestions are `confidential` and `privileged`; in the context of 
> information, these are both used to describe information which *is* shared, 
> but only within a select group. (Think, for instance, of attorney-client 
> privilege: You can share this information with your lawyer, but not with 
> anyone else.)
> 
> So in short, I suggest adding a single access level to the existing system:
> 
>    private
>    fileprivate
>    internal
>    confidential/privileged
>    public
>    open
> 
> This is orthogonal to any other simplification of the access control system, 
> like removing `private` or `fileprivate`.

Yes, I would still like to see scope private go away.

>> Appendix A: file system independence
> 
> I think we need to decide: Is a translation unit of some sort—whether it's a 
> physical on-disk file or some simulacrum like a database record or just a 
> separate string—something intrinsic to Swift? I think it should be; it 
> simplifies a lot of parts of the language that would otherwise require 
> nesting and explicit scoping.
> 
> If translation units are an implicit part of Swift, then this section is not 
> really necessary. If translation units aren't, then we need to rethink a lot 
> of things that are already built in.
> 
> -- 
> Brent Royal-Gordon
> Architechies
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to