[swift-evolution] [Proposal] Qualified Imports and Modules

John McCall rjmccall at apple.com
Mon Jul 18 18:30:18 CDT 2016


> On Jul 18, 2016, at 3:53 PM, Robert Widmann <rwidmann at apple.com> wrote:
> 
> 
>> On Jul 18, 2016, at 3:21 PM, John McCall <rjmccall at apple.com <mailto:rjmccall at apple.com>> wrote:
>> 
>>> 
>>> On Jul 18, 2016, at 2:09 PM, Robert Widmann via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>> Hello all,
>>> 
>>> TJ Usiyan, Harlan Haskins, and I have been working on a proposal to rework qualified imports and introduce an explicit module system to Swift that we’d like to publish for your viewing pleasure.
>>> 
>>> The initial impetus was set out in a radar (rdar://17630570 <rdar://17630570>) I sent fairly early on that didn’t receive a response, so I started a swift-evolution <http://permalink.gmane.org/gmane.comp.lang.swift.evolution/1378> thread discussing the basics of this proposal.  It has been refined and expanded a bit to include an effort to make Swift modules explicit and updated with the feedback of that first thread.  Contents of the proposal are inline and can also be had as a gist <https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6> or on Github. <https://github.com/apple/swift-evolution/pull/440>
>>> 
>>> Cheers,
>>> 
>>> ~Robert Widmann
>>> 
>>> Qualified Imports and Modules
>>> 
>>> Proposal: SE-NNNN <https://gist.github.com/CodaFi/NNNN-first-class-qualified-imports.md>
>>> Authors: Robert Widmann <https://github.com/codafi>, Harlan Haskins <https://github.com/harlanhaskins>, TJ Usiyan <https://github.com/griotspeak>
>>> Status: Awaiting review
>>> Review manager: TBD
>>>  <https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6#introduction>Introduction
>>> 
>>> We propose a complete overhaul of the qualified imports syntax and semantics and the introduction of a module system.
>>> 
>>>  <https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6#motivation>Motivation
>>> 
>>> Swift code is modular by default. However, it is not clear how to decompose existing modules further into submodules. In addition, it is difficult to tell how importing a module affects its export to consumers of a library. This leads many to either fake namespaces with enums, attempt to structure Swift code with modulemaps, or use a large amount of version-control submodules. All of these can be rolled into one complete package in the form of a comprehensive rethink of the qualified import system and the introduction of a module system.
>>> 
>>>  <https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6#proposed-solution>Proposed solution
>>> 
>>> Modules will now become an explicit part of working with canonical Swift code. The grammar and semantics of qualified imports will change completely with the addition of import qualifiers and import directives. We also introduce three new contextual keywords: using, hiding, and renaming, to facilitate fine-grained usage of module contents.
>>> 
>>>  <https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6#detailed-design>Detailed design
>>> 
>>> Qualified import syntax will be revised to the following
>>> 
>>> module-decl -> module <module-path>
>>> import-decl -> <access-level-modifier> import <module-path> <(opt) import-directive-list>
>>> module-path -> <identifier>
>>>             -> <identifier>.<import-path>
>>> import-directive-list -> <import-directive>
>>>                       -> <import-directive> <import-directive-list>
>>> import-directive -> using (<identifier>, ...)
>>>                  -> hiding (<identifier>, ...)
>>>                  -> renaming (<identifier>, to: <identifier>, ...)
>>> This introduces the concept of an import directive. An import directive is a file-local modification of an imported identifier. A directive can be one of 3 operations:
>>> 
>>> 1) using: The using directive is followed by a list of identifiers within the imported module that should be exposed to this file. 
>>> 
>>> // The only visible parts of Foundation in this file are 
>>> // Date.init(), Date.hashValue, and Date.description.
>>> import Foundation.Date using (Date.init(), Date.hashValue, Date.description)
>>> 2) hiding: The hiding directive is followed by a list of identifiers within the imported module that should be hidden from this file.
>>> 
>>> // Imports all of Foundation.Date except `Date.compare()`
>>> import Foundation.Date hiding (Date.compare())
>> It's unfortunate that this proposal requires the identifiers to be re-qualified when the imported module is actually the name of a type.
> 
> I considered making that a feature of this proposal but I had a tough time reconciling removing the decl specifier from qualified imports and having an unambiguous notation for Swift declarations. 

Yes, I can see that there are difficulties here.  I'm just worried that an abruptly-designed solution is going to carve those difficulties into stone.

>>  It seems to me that whether e.g. a type is defined in its own sub-module is a detail that users won't really appreciate and which probably shouldn't be surfaced to them.  In fact, in general I'm concerned about this turning the file and directory organization of a project into API.
> 
> It’s a detail that they’ve already had to have surfaced if they use the existing syntax.

Hmm?  We don't have sub-modules today.  "import Foundation.NSObject" just brings that specific declaration in; there's no semantic differentiation between that and importing a module.

Relatedly, your proposal side-steps any discussion about what happens if you try to name a sub-module the same as a type.  You must have done that very carefully, because one of your examples clearly envisages String being in its own sub-module. :)

>>  In fact, in general I'm concerned about this turning the file and directory organization of a project into API.
> 
> It’s a legitimate concern and one that I share considering there is a way to abuse this restriction (*cough* Java), or try to rally around a limited set of namespaces for common controls.
> 
>> 
>>> This proposal also solves the problem of module export. A module that is imported without an access level modifier will default to an internal import per usual. However, when it is useful to fully expose the public content of submodules to a client, a public modifier can be used. Similarly, when it is useful to access internal or [file]private APIs, but not expose them to clients, those access modifiers may be used.
>>> 
>> These uses of access modifiers feel inconsistent to me.  "public import Foo" exports the contents of Foo as if they were members of my module, but "private import Foo" imports the private APIs (?) of Foo?  That is not the same interpretive rule.
>> 
> 
> It’s not, and it’s that way intentionally.  Non-public imports do not re-export.  The wording around this is shabby, I’ll work to improve it.

Yeah, I don't think this is good.  I think my suggestion makes more sense, where a non-private import exports to whatever the named scope is.  This draws the current behavior consistently into your model (the default import rule is private), and the "internal import" concept actually seems like a pretty useful feature in a world where you anticipate people doing significant renames on import.

>> I think the more consistent analogy for "private import" would be to say that the public members of Foo are visible in this file only (i.e. the default behavior of "import" today, which I think we would want to keep), whereas you could do an "internal import" to make the members of Foo visible throughout the current module (potentially useful if you have an interesting set of common modifications you want to make).
>> 
> 
>> I don't know why you think it should be possible to import the private declarations of a module.  That seems completely contrary to the access-control design.  I agree that it's useful to have sub-modules expose APIs that are only usable by other parts of the larger module, but I think the Swiftier design would be for that to be opt-in on the declaration somehow, or at least to specify how it interacts with "internal".
>> 
> 
> I was approached by users at WWDC that did wish to have some way of grouping a bunch of private Swift files that should “know about each other’s internal stuff”.  At the time this was the semantics that seemed to match that and stayed in-line with what a `private import` could possibly do.  Perhaps this kind of import can be banned-by-diagnostic in that case.

This sounds more like it's calling for better definition of the access interactions between sub-modules.

>> Also, it is completely unclear to me why modifiers like "renaming" don't change how the imported module's declarations are re-exported.
>> 
> 
> Because importing a library shouldn’t be able to change whatever it likes and break client code on re-export.  I don’t have a particularly compelling use-case for allowing user-specified mappings to escape file scope and neither do many other languages I can find that permit this feature.  If you have one I’d like to know about it.

Sharing user-specified mappings between files seems better than forcing them to be copy-and-pasted.  You could lock down on public exports that rename mappings if you're worried about that.

John.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160718/26b703b3/attachment.html>


More information about the swift-evolution mailing list