Our import story definitely needs work, and this is a step in the right direction. Thanks for working on this! Some comments:
- The import changes can be separated from the submodule issues. Enhancing imports is IMO more important, and is source-breaking today, whereas 'module ' declarations and submodules can be added later. I'd suggest breaking this into two proposals. - I think the `import` design you propose is a bit more complicated than it needs to be. Python and Haskell get by just having "import everything" and "import itemized (with aliases)". I don't see the need for 'hiding'; if you have a rule that itemized imports get priority over import-everything, then that covers the most important use case of selectively shadowing one module's imports with another. Bikeshed-wise, I don't see much reason to veer from the Java/Haskell-ish template of: import Foo.* // import everything from module Foo import Foo.(x, y, z as zed) // import x, y, and z from foo, renaming Foo.z to zed -Joe > On Jul 18, 2016, at 2:09 PM, Robert Widmann via swift-evolution > <[email protected]> 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) I sent fairly > early on that didn’t receive a response, so I started a swift-evolution > 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 or on Github. > > Cheers, > > ~Robert Widmann > > Qualified Imports and Modules > > • Proposal: SE-NNNN > • Authors: Robert Widmann, Harlan Haskins, TJ Usiyan > • Status: Awaiting review > • Review manager: TBD > Introduction > > We propose a complete overhaul of the qualified imports syntax and semantics > and the introduction of a module system. > > 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. > > 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. > > 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()) > 3) renaming: The renaming directive is followed by a list of identifiers > separated by to: that should be exposed to this file but renamed. > > // Imports all of Dispatch.DispatchQueue but renames the static member > // DispatchQueue.main, to DispatchQueue.mainQueue > import Dispatch.DispatchQueue renaming (DispatchQueue.Type.main to: > DispatchQueue.Type. > mainQueue) > > // Renaming can also rename modules. All members of UIKit have to be > qualified with > // `UI` now. > import UIKit renaming (UIKit, to: UI) > Import directives chain to one another and can be used to create a > fine-grained module import: > > // Imports all of Foundation except `DateFormatter` and renames `Cache` to > `LRUCache` > import Foundation > hiding (DateFormatter) renaming (Cache to: LRUCache) > > // Imports SCNNode except SCNNode.init(mdlObject:) and renames `.description` > to > // `.nodeDescription` > import SceneKit > using (SCNNode) > renaming (SCNNode > .description, to: SCNNode. > nodeDescription) > hiding (SCNNode > .init(mdlObject:)) > Directive chaining occurs left-to-right: > > // This says to 1) Hide nothing 2) Use nothing 3) rename Int to INT. It is > invalid > // because 1) We will show everything 2) Then hide everything 3) Therefore > Int is unavailable, error. > import Swift hiding () using () renaming (Int > , to: INT) > > // This says to 1) Use Int 2) Hide String 3) rename Double to Triple. It is > invalid > // because 1) Int is available 2) String is not, error. 3) Double is > unavailable, error. > import Swift using (Int) hiding (String) renaming (Double > , to: Triple) > > // Valid. This will be merged as `using (Int)` > import Swift using () using (Int > ) > > // Valid. This will be merged as `hiding (String, Double)` > import Swift hiding (String) hiding (Double > ) hiding () > > // Valid (if redundant). This will be merged as `using ()` > import Swift using (String) hiding (String) > Module scope is delimited by the keyword module followed by a fully qualified > name and must occur as the first declaration in a file. For example: > > // ./Math/Integers/Arithmetic.swift > > module Math > .Integers. > Arithmetic > > > public protocol > _IntegerArithmetic {} > > > public struct > _Abs {} > > > @_versioned > internal func _abs<Args>(_ args: Args) -> > (_Abs, Args) {} > > > // ./Math/Integers.swift > > module Math > . > Integers > > > // _abs is visible in this module and all others within the project, > // but is not exported along with it. > internal import Math.Integers.Arithmetic > > > > public protocol IntegerArithmetic : _IntegerArithmetic, Comparable > {} > > public protocol SignedNumber : Comparable > , ExpressibleByIntegerLiteral {} > > > > // Math.swift > > module Math > > > // Exports the entire public contents of Math.Integers, but nothing in > // Math.Integers.Arithmetic. > public import Math.Integers > 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 > 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. For example: > > // A submodule declaring a `private` class that gets imported with > // an `internal` qualifier with a `using` directive is an invalid import > // declaration. > > module Foo > . > Bar > > > private class > PrivateThing {} > > module Foo > > > // Error: PrivateThing not visible, use `private import` > import Foo.Bar using (PrivateThing) > // However, a submodule declaring a `public` struct that gets imported with > // an `private` qualifier is a valid import declaration. > > module Foo > . > Bar > > > public class > PublicThing {} > > module Foo > > > // All good! Foo can see Foo.Bar.PrivateThing. > private import Foo.Bar using (PublicThing) > 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. > > // In this file and this file alone, the directives apply. To the user > // of this module, it is as though this declaration were simply: > // public import Foundation.Date > public import Foundation.Date hiding (Date.init > ()) > renaming (Date > .Type. > distantPast, > to: Date > .Type. > letsGoLivingInThePast, > Date > .Type. > timeIntervalSinceReferenceDate, > to: Date > .Type. > startOfTheUniverse) > renaming (Date > .Type.<, to: Date.Type.<<<<<) > Impact on existing code > > Existing code that is using qualified module import syntax (import > {func|class|typealias|class|struct|enum|protocol} <qualified-name>) will be > deprecated. Code that is not organized into modules will remain unaffected > and organized into one contiguous top-level module. However, it is strongly > recommended that frameworks be decomposed and reorganized around the new > module system. > > As a case study, the public interface to the standard library appears to > already be mostly broken down into submodules as described in GroupInfo.json. > > Code that is defined in modulemaps already defines a module structure that > can be imported directly into this scheme. > > Alternatives considered > > Module export can also be placed on the module declaration itself. The > relevant parts of the grammar that have changed are below with an example: > > module-decl -> <access-level-modifier> module <module-path> > import-decl -> import <module-path> <(opt) import-directive-list> > > private module String.Core. > Internals > > > // Shh, it's a secret. > While this style makes it immediately obvious to the library author which > modules are public or private, it causes the consumer problems because > submodule exports are no longer explicit and are entirely ad-hoc. In the > interest of enabling, for one, users of IDEs to drill into public submodules, > making export local to import seems more appropriate. > _______________________________________________ > 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
