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

Brent Royal-Gordon brent at architechies.com
Mon Jul 18 18:34:38 CDT 2016


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

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

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

* 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?

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

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

> 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



More information about the swift-evolution mailing list