> On Mar 3, 2017, at 9:24 AM, Karim Nassar via swift-evolution > <swift-evolution@swift.org> wrote: > > > I’ve read through the last couple of Swift (sub)Module proposals put forward, > and since my particular use-cases for a sub-module solution seemed to be > under-served by them, I’ve decided to write up my thoughts on the matter to > prompt discussion. > > Perhaps my use-cases are outliers, and my approach will be deemed naive by > the community… I’m happy to learn better ways of doing things in Swift, and > welcome any thoughts, criticism, or illumination related to these ideas. > > I’m including the write-up below, but it’s also available as a gist: > https://gist.github.com/anonymous/9806f4274f1e13860670d6e059be5dce > > — > > # Sub-modules > > A sub-module solution in Swift should have the following properties: > > * Extremely light-weight > * Low API surface area > * Adopt progressive disclosure > * Integrate with Access Control features to enable a level of encapsulation & > hiding between the Module and File level > * Be permeable when desired > > ## Discussion > > As we get deeper into building real applications & frameworks with Swift, we > begin to realize that having a way to express relationships between types is > desireable. Currently, Swift only allows us to express these relationships > at two levels, the Module and the File. > > The Module boundary is acceptable for small, focused frameworks, while the > File boundary is acceptable for small, focused Types, but both levels can be > unweildy when dealing with certain cases where a cluster of internally > related types needs to know about each other but may only want to publish a > narrow set of APIs to the surrounding code, or in large complex applications > which are necessarily structured as a single Module. In these cases, we wind > up with large monolithic Modules or (even worse) large monolithic Files. > > I have seen this proliferation of Huge Sprawling Files (HSFs) in my own code, > and seek a way to combat this rising tide. > > ## Goals > > It is a goal of this proposal to: > > * Suggest a mechanism for organizing code between the Module and File levels > that is as lightweight and low-friction as possible > * Provide mechanisms for authors to create both "hard" and "soft" API > boundaries between the Module and File levels of their code > > ## Anti-Goals > > It is not a goal of this proposal to: > > * Move Swift away from filesystem-based organization > * Significantly alter the current Access Control philosophy of Swift > > ## Proposal Notes > > Please take the following proposal wholely as a Straw-Man... I would be > equally satisfied with any solution which meets the critera described at the > top of this document. > > Unless specified otherwise, all spellings proposed below are to be considered > straw-men, and merely illustrative of the concepts. > > ## Proposed Solution > > Two things are clear to me after using Swift and following the Swift > Evolution list since their respective publications: > > 1. Swift has a preference for file-based organization > 2. Vocal Swift Users dislike `fileprivate` and want to revert to Swift2-style > `private` > > Because of #1, this proposal does not seek to change Swift's inherent > file-system organization, and instead will expand on it. > > Since I personally fall into the camp described by #2, and most of the > community response to this has been "Lets wait to deal with that until > sub-modules", I'm making this proposal assuming that solving that quagmire is > in-scope for this propsoal. > > ### Changes to Access Control Modifiers > > As part of this proposal, I suggest the following changes to Swift 3's Access > Control modifiers: > > * Revert `private` to Swift 2's meaning: "hidden outside the file" > * Remove `fileprivate` as redundant > > This is potentially a source-breaking change. However, it is interesting to > note that this change is **not** required for the following proposal to > function. > > Changes that *are* necessary are: > > * Change the spelling of `internal` to `module` (making `module` the new > default) > * Introduce a new modifier `internal` to mean "Internal to the current > sub-module and its child-sub-modules”
Can you give concrete examples of use cases where a descendent submodule needs access to symbols declared by an ancestor? I gave some thought to this when drafting my proposal and came to the conclusion that this runs against the grain of layering and is likely to be a bad idea in practice. If there are use cases I didn’t consider I am very interested in learning about them. > > These changes are *not* source-breaking because the new `internal` modifier > acts exactly as the old `internal` modifier unless it is used within a > sub-module. The specific spelling of this new `internal` modifier is > necessary to maintain backwards source compatibility. > > The new `module` modifier allows authors to make APIs permeable between > sub-modules while still hidden outside the owning Module if desired. > > All other Access Control modifiers behave the same as they currently do > irrespective of sub-module boundaries, so: > > * `public` => Visible outside the Module > * `open` => Sub-classable outside the Module > > ### Making a Sub-module > > To create a sub-module within a Module (or sub-module) is simple: The author > creates a directory, and places a "sub-module declaration file" within the > directory: > > ``` > // __submodule.swift Why the double underscore prefix? To make it sort to the top in a file browser? Is this file allowed to have any Swift code? Or is it limited to submodule-related declarations only? If the latter, why not use an extension such as `.submodule` or `.swiftmodule` to differentiate it from ordinary Swift files and allow the submodule to be named by the name of this file? > // MyModule > > submodule SubA > > ``` > > Then any files within that directory are part of the sub-module: > > ``` > // Foo.swift > // MyModule.SubA > > struct Foo { > private var mine: Bool > internal var sub: Bool > module var mod: Bool > } > > public struct Bar { > module var mod: Bool > public var pub: Bool > } > > ``` > > This creates a sub-module called "SubA" within the module "MyModule". All > files within the directory in which this file appears are understood to be > contained by this sub-module. > > If in the future we choose to add additional complexity (versioning, > #availability, etc) to the sub-module syntax, the sub-module declaration > gives a natural home for this configuration. > > It's important to note some benefits of this approach: > > * Using the "special file" means that not all Directories are automatically > submodules > * Any given source file may only be a member of 1 submodule at a time > * Use of filesystem structure to denote sub-modules plays nicely with source > control > * The sub-module structure is instantly clear whether using an IDE (which can > be taught to parse the `__submodule.swift` files to decorate the UI), or > simple text-editor (assuming a convention of naming the Directory the same as > the sub-module, which is a linter problem) If we’re going to use the file system to organize submodules this seems like a reasonable approach. It allows larger submodules to have folder hierarchies within them and also creates a central location for submodule-related declarations. A primary flaw I see in this approach is that Xcode is the dominant IDE for Swift and the way Xcode handles files is not conducive to file-system organization. I really detest the way Xcode handles this and would vastly prefer that it simply reflected the physical file system hierarchy but I don’t think that will change any time soon. On the other hand maybe a file system based submodule system in Swift would motivate the Xcode team to better reflect the physical file system organization. > > ### Using Sub-modules > > Referencing a sub-module should be natural and clear at this point: > > #### From Within the Parent Module/Sub-module > > Sub-modules are simply code-organization & namespacing tools within modules. > As such, when referenced from within their parent Module, there is no need > for `import`s > > ``` > // in MyModule > > let foo = SubA.Foo() > foo.mine = true // Compiler error because it's private > foo.sub = true // Compiler error because it's internal to the sub-module > foo.mod = true // OK > > ``` > > #### From Outside the Parent Module/Sub-module > > When referenced from outside their parent Module, one imports the whole > module in the standard way: > > ``` > import MyModule > > let foo = SubA.Foo() // Compiler error because it's internal to the Module > > let bar = SubA.Bar() // OK > bar.mod = true // Compiler error because it's internal to the Module > bar.pub = true // OK > > ``` > > ## What this Proposal Deliberately Omits > > This proposal deliberately omits several concepts which may be integral to > various use-cases for sub-modules, primarily because they can be treated as > purely additive concepts and I don't wish to weigh down the consideration of > the overall approach with a larger API surface area that might be debated > separately. I.e: Keep it as small as possible for now, then if it's any good, > iterate on the design. > > ### Inter-Sub-Module Access Control > > One might ask given a sub-module structure like: > > ``` > MyModule > | > +--- SubA > | > +--- SubB > > ``` > > "How can SubB hide properties from MyModule without hiding them from SubA?" > > This is a valid question, and not answered by this proposal for two reasons: > > * This trivial case could be solved by simply adding a new modifier > `submodule` if we so desired, but: > * In the absence of any direct response, the status-quo provides a > work-around: Omit the sub-sub-module structure and use the file-access > constraints of `private` > * This overall problem probably should be solved by addressing larger > questions in the Access Control scheme of Swift, irrespective of the > sub-module mechanism > > ### Expressiveness of Sub-module Imports > > One might ask: "Why can't I import only a specific sub-module or alias a > sub-module?" > > I have ignored this aspect of submodules because the question of `import` > expressiveness is a separate issue in my mind. The fact that we cannot say: > > ``` > import MyModule as Foo > ``` > > Has no relationship to the lack of sub-modules in Swift. > > If the community deems it an important enough use-case to warrant altering > import behavior, so be it, but that can be treated as purely additive to this > proposal. > > But it should be understood that this approach to sub-modules is not designed > to provide an expressive "exports" capability. It is primarily interested in > organizing code *within* a Module > > > > > _______________________________________________ > 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