I believe `submodule` in my proposal introduces a segmented-scope-like thing, akin to how the various files included in a module are combined as one collection of declarations. And you have the ability to import things from that scope with `import submodule`, like with modules. I think the name submodule fits as some sort of "lesser module", but if someone has a better name for this I'll listen.
We could add a submodule-private access level, but I'm not sure it is worth its weight. There's plenty of possibilities, but I know I wouldn't be too happy with more access levels than what we have now. For each access level you add the more fine-grained they become and the more difficult it is to decide which one is the right one. Hence why I'm keeping the idea of adding access levels as a separate concern worth of its own debate. This proposal is solely focused on providing intra-module boundaries, and I think those boundaries can work without introducing a new access level. > On 6 mars 2017, at 2:39, Robert Widmann <[email protected]> wrote: > > If this were a problem that could be fixed by tooling, you'd be submitting > this proposal against the package manager. > > It's important to note I'm approaching this problem from the principles that > underpin modular programming practices in other languages, it is not merely > "philosophical". A proposal that claims to support submodules should come > with some consistent support for modularity or rebrand to more honestly > reflect its contents. As it stands, this proposal effectively introduces > another level of access (while paradoxically claiming to avoid access control > discussions), and suggests more access control to support its contents later. > Maybe discussion should start there instead of with "submodules". > > ~Robert Widmann > > 2017/03/06 0:39、Michel Fortin <[email protected] > <mailto:[email protected]>> のメッセージ: > >> >>> On 5 mars 2017, at 22:28, Robert Widmann <[email protected] >>> <mailto:[email protected]>> wrote: >>> >>> This proposal strikes me not as a submodules proposal, but as a sneaky way >>> to add 'friend' to Swift. It is conceptually simpler because it doesn't >>> address modularity at all! >>> >>> ~Robert Widmann >>> >>> 2017/03/05 17:16、Michel Fortin via swift-evolution >>> <[email protected] <mailto:[email protected]>> のメッセージ: >>> >>>> Sorry for introducing yet another submodule proposal out of the blue. >>>> >>>> I'm a bit surprised at how far-reaching the various submodule proposals >>>> floated on this list have been. Directories, access controls, @exported >>>> imports... For comparison's sake here's one that is *really* simple and >>>> short I wrote today. Best of all: it requires no new access modifier. >>>> >>>> I also expect everyone to scream at it because it does not include all the >>>> desirable features of a submodule system. At the very least I'll have >>>> redefined the meaning of lightweight in that discussion. Good reading. >>>> >>>> Also available here: >>>> https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18 >>>> <https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18> >>>> >>>> >>>> ## Motivation >>>> >>>> The goal of this proposal is to provide lightweight intra-module >>>> boundaries so you can avoid exposing every `internal` declaration within a >>>> big module to all other files in the module. >>> >>> I don't think this was a stated goal of anybody because that’s not the >>> primary issue submodules are trying to solve. Projects with a large amount >>> of internal declarations may be clamoring to be put into multiple >>> (sub)modules, but because the style most have converged on doesn’t usually >>> involve a large amount of free functions, your internal declarations are >>> usually types and as such have members that are already encapsulated. >> >> Well maybe that's just a problem specific to me, but that's the goal I'd >> like submodule to solve. If I have four intermingled classes, I'd like to >> restrict the interminglement to those four classes. My current options for >> that are a) to create a separate module, b) to put them all in the same file >> with `fileprivate`, or c) tolerate that the rest of the module will see >> everything all the time with `internal`. I'm choosing (c) at the present >> time because (a) is too much work and (b) is more messy. >> >> You are absolutely right in your observation that it's some sort of >> "friend". But then the same observation could apply to the `internal` >> members in a module or the `fileprivate` members in a file. The delimiting >> boundaries are different, but the concept is the same. What I want is the >> ability to better define those boundaries. >> >>>> Not a goal: addressing the file-level access fileprivate/private or >>>> scoped/protected debates. This is left to other proposals. >>>> >>>> >>>> ## Summary >>>> >>>> This proposal adds the declarations `submodule` and `import submodule`. It >>>> also limits the visibility of `internal` to files with a matching >>>> `submodule` or `import submodule` declaration. >>>> >>>> Submodules are never exposed outside of the module. They only change the >>>> visibility of internal declarations, so there is no point in exposing them >>>> publicly. >>>> >>>> Submodules are not bound to directories, nor are they necessarily >>>> hierarchical. >>>> >>>> This change is purely additive and introduces no source compatibility >>>> issue. >>> >>> This particular set of changes is, but the `import submodule` change is >>> source-breaking in that we already have syntax for this. >> >> Good catch. If you have a module called "submodule" and you import it, >> that'll break. >> >>>> >>>> >>>> ## Details >>>> >>>> A `submodule <name>` declaration at the beginning of a file contains an >>>> identifier with the submodule name: >>>> >>>> submodule Foo >>>> >>>> internal func foo() {} >>>> public func pub() {} >>>> >>>> `internal` declarations within that file are only visible to other files >>>> sharing the same submodule name. The submodule only protects `internal` >>>> declarations: `public` declarations in the file are visible everywhere (in >>>> other submodules and in other modules). >>>> >>> >>> This definition of public is anti-modular. It seems as though this style >>> of submodule will encourage people to split their definitions across >>> multiple files (good) then provide a pile of submodule-membership >>> definitions to intertwine them with each other as they need access to each >>> other’s symbols (bad). A module is a self-contained entity by definition. >>> Dependencies should generally be on further child (sub)modules - >>> vertically, not horizontally. >> >> I'll say that this definition of `public` is quite handy as long as the >> submodules are implementing public-facing functionality. I agree it is less >> useful when the submodule is the backend of some other component in the >> module. You are thus right in that it naturally organizes things >> horizontally. >> >> So if you want to use submodules to organize self-contained dependencies, my >> approach does not work very well. In particular, there is no containment of >> anything `internal` when you `import submodule Foo`. That would be improved >> if we had submodule-private as discussed in Future Directions... with the >> caveat that it's still easy to access any submodule's internals by declaring >> any file to be part of the submodule. And also you want a strict hierarchy, >> which this proposal completely eschew. >> >> We obviously have a different philosophy on how strict and hierarchic the >> submodules should be. I say the strictness should stay at the module >> boundaries. If you want enforced self-contained modules, change those >> submodules into full modules. If you want to ship the whole thing as a >> single library, then we need to adapt the tooling so you can merge privately >> those modules inside of the main library. (Ideally this would be done with a >> single compiler invocation, allowing full cross-module optimization.) This >> is a tooling and packaging problem that does not need to leak into a >> plethora of new language concepts. At least, this is how I see it. >> >> >>>> A file can be part of more than one submodule: >>>> >>>> submodule Foo >>>> submodule Bar >>>> >>>> internal func achoo() { >>>> foo() // available in Foo (from other file) >>>> } >>>> >>>> This makes the internal `achoo` function visible within both the `Foo` and >>>> `Bar` submodules. Also note that it makes internal members of both >>>> submodules `Foo` and `Bar` visible within the file. >>>> >>>> A file can access internal declarations of a submodule without having to >>>> expose its own internal functions to the submodule with `import submodule`: >>>> >>>> submodule Test >>>> import submodule Foo >>>> >>>> internal func test() { >>>> foo() // internal, imported from Foo >>>> achoo() // internal, imported from Foo >>>> pub() // public, so always visible >>>> } >>>> >>>> Finally, when a file has no submodule declaration, its internal >>>> declarations are visible everywhere in the module and all its submodules: >>>> >>>> --- Hello.swift --- >>>> // no submodule declaration >>>> internal func hello() {} >>>> >>>> --- World.swift --- >>>> submodule World >>>> internal func test() { >>>> hello() // visible! >>>> } >>>> >>>> >>>> ## Nitpicky Details (Conflicting Declarations) >>>> >>>> Declaring `internal` things that share the same name in two separate >>>> submodules is not a conflict: >>>> >>>> --- Foo1.swift --- >>>> submodule Foo1 >>>> class Foo {} // added to Foo1 >>>> >>>> --- Foo2.swift --- >>>> submodule Foo2 >>>> submodule Foo3 >>>> class Foo {} // added to Foo2 and Foo3 >>>> >>>> (Note: It would be a conflict if one of them was `public`, because >>>> `public` declarations are always visible everywhere inside (and outside >>>> of) the module.) >>>> >>>> Attempting to use both from another submodule will create ambiguity >>>> errors. You can disambiguate using the submodule name as a prefix: >>>> >>>> --- Main.swift --- >>>> import submodule Foo1 >>>> import submodule Foo2 >>>> import submodule Foo3 >>>> let f0 = Foo() // ambiguity error >>>> let f1 = Foo1.Foo() // good >>>> let f2 = Foo2.Foo() // good >>>> let f3 = Foo3.Foo() // good >>>> >>>> Best to avoid this for your own sanity however. >>>> >>>> >>>> ## Alternatives Considered >>>> >>>> ### Conflicting Declarations >>>> >>>> Instead of allowing conflicting symbols in different submodules, we could >>>> continue to disallow conflicting `internal` declarations even when they >>>> belong to different submodules. This would make the design simpler, as it >>>> is closer to how `internal` currently works and prevent ambiguity errors >>>> from arising when importing multiple submodules. The behavior would be a >>>> little surprising however. >>>> >>>> We could also simplify by removing the ability to use the submodule name >>>> as a prefix to disambiguate. This has the advantage that if you put a type >>>> inside of a submodule with the same name, no conflict can arise between >>>> the name of the type and the name of the submodule. Disambiguation would >>>> have to be done by renaming one of the conflicting declarations. Since >>>> this ambiguity can only affect `internal` declarations (submodules only >>>> group internal declarations), requiring a rename will never break any >>>> public API. But forcing a rename is not a very elegant solution. >>>> >>>> ### `import` syntax >>>> >>>> Renaming `import submodule` to `import internal`. Having "internal" in the >>>> name could make it clearer that we are giving access to internal >>>> declarations of the submodule. But it also make the import less relatable >>>> to the `submodule` declaration in other files. >>>> >>>> >>>> ## Future Directions >>>> >>>> ### Submodule-Private >>>> >>>> While a submodule-private access modifier could have been added to this >>>> proposal, the belief is that this proposal can live without it, and not >>>> having this greatly reduce the little details to explore and thus >>>> simplifies the design. >>>> >>>> In many cases you can work around this by putting "private" stuff in a >>>> separate submodule (somewhat similar to private headers in C land). For >>>> instance: >>>> >>>> --- Stuff.swift --- >>>> submodule Stuff >>>> submdoule StuffImpl >>>> >>>> func pack() { packImpl() } >>>> >>>> --- StuffImpl.swift --- >>>> submodule StuffImpl >>>> >>>> func packImpl() { ... } >>>> >>>> This will not work for stored properties however. A future proposal could >>>> suggest allowing stored properties in extensions to help with this. >>>> >>>> And a future proposal could also add submodule-private to limit visibility >>>> of some declarations to only those files that are part of a specific >>>> module. >>>> >>>> >>>> -- >>>> Michel Fortin >>>> https://michelf.ca <https://michelf.ca/> >>>> >>>> _______________________________________________ >>>> swift-evolution mailing list >>>> [email protected] <mailto:[email protected]> >>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >> >> -- >> Michel Fortin >> https://michelf.ca <https://michelf.ca/> -- Michel Fortin https://michelf.ca
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
