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

Robert Widmann rwidmann at apple.com
Mon Jul 18 19:08:13 CDT 2016


> On Jul 18, 2016, at 4:34 PM, Brent Royal-Gordon <brent at architechies.com> wrote:
> 
>> On Jul 18, 2016, at 2:09 PM, Robert Widmann via swift-evolution <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.
> 
> This is really interesting. A few incomplete comments:
> 
>> 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())
> [snip]
>> // Exports the entire public contents of Math.Integers, but nothing in 
>> // Math.Integers.Arithmetic.
>> public import Math.Integers
> 
> Would this work?
> 
> 	module UIKit
> 	
> 	public import UIKit.UIGestureRecognizerSubclass.UIGestureRecognizer
> 		hiding (UIGestureRecognizer.touchesBegan(_:with:), UIGestureRecognizer.touchesMoved(_:with:), …)
> 
> (If so, we'd need a way to hide only the setter of a property, not the getter.)

Yes, that would work.  Notation for hiding setters and getters through the import system (probably best to do it internally, but I see what you mean) can ideally be another dot qualifier

public import Foo hiding (Foo.bar) // Hide the whole thing
public import Foo hiding (Foo.bar.set) // Hide the setter.
public import Foo hiding (Foo.bar.get) // Hide the getter [Not sure why you’d want this].

>> Modules names are tied to a directory structure that describes their location relative to the current module and it will now be an error to violate this rule. For example:
>> 
>> module String // lives in ./String.swift
>> 
>> module String.Core // lives in ./String/Core.swift
>> 
>> module String.Core.Internals.Do.You.Even.Write // lives in ./String/Core/Internals/Do/You/Even/Write.swift
> 
> I think this is a mistake for several reasons:
> 
> * You may need to split a single submodule across multiple files, but this rule doesn't allow that.
> 
> * The module declaration and filename contain redundant information, and one of them could get out of sync with the other.
> 
> * Xcode doesn't like to organize things into folders on disk and will fight you tooth and nail.
> 
> * Deeply nested folders are a usability issue. Never forget the jury in Oracle v. Google: https://www.geek.com/wp-content/uploads/2016/05/courtroomhijinks.png
> 
> At the very least, I would like to see allowances for multi-file submodules—String/Core/Internals/Do/You/Even/Write**.swift. Better would be to use long filenames—String.Core.Internals.Do.You.Even.Write*.swift. Even better would be to just allow freeform naming and trust programmers to organize their projects sanely.

We are not proposing a Java-style module system so much as an extension of the existing one to submodules the way they are used today for frameworks like Darwin.  Projects no longer require reverse-DNS-style directory structures and nesting of submodules to great depth can grow unwieldy, but that may be a sign that a project is growing too decentralized.  Large frameworks will decompose themselves into submodules in roughly the same way that projects written in Objective-C, C, C++ have always decomposed themselves.  The directory structure for that style of framework rarely grows to the extend you’re concerned with here.  


>> Existing projects that do not adopt these rules will still retain their implicit module name (usually defined as the name of the framework or application that is being built) and may continue to use whatever directory structure they wish, however they may not declare any explicit modules.
>> 
>> 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. The rule of thumb is: Only identifiers that are at least as visible as the qualifier on the import make for valid import declarations.
> 
> It appears the semantics here are "grab all declarations at level X and above, and expose them in this module as level X". This is bizarre for a number of reasons:
> 
> * Currently, `import` can only access `internal` declarations using `@testable`. This would change that.
> 

It would not.  Re-export can only re-export public APIs.

> * Currently, `import` can't access `private` and `fileprivate` declarations at all, and it's not clear what it would mean to add that. What happens if two different parts of the module have different `private` members with the same name? Which do you get?

If we decide to allow this you will get an ambiguity which you can resolve with a renaming.  John has made me reconsider the semantics of a private import.

> * Currently, `import` only affects the current file—it's effectively "import `public` members as `fileprivate`". If your default is `internal import`, that would imply that an import statement in one file would, by default, expose the APIs it imported to all files. That's an important change in behavior.
> 

It changes existing behavior in the sense that internal imports are now module-scoped rather than project-scoped but you still cannot re-export non-public APIs.  I specifically want to *remove* the behavior where an imported API winds up recursively importing submodules until your qualified import is just garbage anyway.  Your submodules will not see an `internal import` in the same way that a project can currently see an internal import unless they themselves declare an `internal import` of that module.  And if they’re re-exporting public APIs then you probably wanted to see that when you imported them anyway.

> I think you're mixing two things together that ought not to be. `import` should always import only public APIs (unless you use `@testable`—which might need a bit of renaming to support the additional use case of SPIs between submodules and supermodules—in which case you also get `internal` APIs).

And it does.

> An access modifier on the `import` statement controls how they're exposed to the rest of the file/project/world, and `private` is the default. It's a little weird to have `private` be the default on `import` when `internal` is the default on everything else, but the alternative is to change `import`'s behavior in a way that is neither backwards-compatible, nor likely to be correct.

Internal is the default.

> 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.

Doesn’t mean you can import internal APIs from modules you don’t own (that’s still banned).  You can still only export public API.

> 
>> Because import directives are file-local, they will never be exported along with a public import and will default to exporting the entire contents of the module as though you had never declared them.
> 
> Whoa, wait, what? Why? I think hiding parts of the implementation is a good use case for re-exporting a module. And I think the clear implication of attaching these clauses directly to the import statement is that it controls how the module is imported everywhere that statement makes it visible, not just within the one file. If we're not going to do that, I think these clauses ought to be separated from the `import` statement and turned into something separate.
> 
> -- 
> Brent Royal-Gordon
> Architechies
> 

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


More information about the swift-evolution mailing list