On Mon, Jul 18, 2016 at 4: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 > <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()) > > 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.mainQueueimport 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) > > I don't mean to detract from the conversation on the deeper design aspects of this proposal, but in terms of the proposed syntax I think there's opportunity for simplification. I understand the desire to use parentheses, especially to avoid taking up `to` even as a contextual keyword, but I think it is possible to improve clarity by way of some tweaks: ``` import Swift using Int import Swift using Int as INT, Double as Triple // there is no possible confusion with casting here, // and the use of `as` in this context would be familiar // for those coming from some other languages import Swift using Int, Double as Triple, String as _ // poof, no more 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 {} > @_versionedinternal 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.Datepublic 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.<<<<<) > > > <https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6#impact-on-existing-code>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 > <https://github.com/apple/swift/blob/master/stdlib/public/core/GroupInfo.json> > . > > Code that is defined in modulemaps already defines a module structure that > can be imported directly into this scheme. > > <https://gist.github.com/CodaFi/42e5e5e94d857547abc381d9a9d0afd6#alternatives-considered>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
