> On Mar 3, 2017, at 9:24 AM, Karim Nassar via swift-evolution
> <[email protected]> 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
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution