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

Robert Widmann rwidmann at apple.com
Mon Jul 18 17:21:07 CDT 2016


>  is changed so that, outside the module, it has a member `frobnicate()` and no longer has a member `bobnicate()`?

*Unchanged.  Other than that, you’ve got it!  I wanted to make *very* sure that whatever APIs come in are the same ones that go out regardless of local changes.

> On Jul 18, 2016, at 3:17 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
> 
> On Mon, Jul 18, 2016 at 5:09 PM, Robert Widmann <rwidmann at apple.com <mailto:rwidmann at apple.com>> wrote:
> 
>> On Jul 18, 2016, at 3:00 PM, Xiaodi Wu <xiaodi.wu at gmail.com <mailto:xiaodi.wu at gmail.com>> wrote:
>> 
>> On Mon, Jul 18, 2016 at 4:49 PM, Robert Widmann <rwidmann at apple.com <mailto:rwidmann at apple.com>> wrote:
>> 
>>> On Jul 18, 2016, at 2:32 PM, Xiaodi Wu <xiaodi.wu at gmail.com <mailto:xiaodi.wu at gmail.com>> wrote:
>>> 
>>> This is an interesting document. I think it deserves careful study. For now, some questions:
>>> 
>>> What is the rationale behind permitting the using of specific methods? This seems to be usually fine-grained in comparison to other languages. What use cases do you have in mind for this?
>>> 
>> 
>> One use case: Swift libraries export not just member references as I’ve used here, but a large amount of free functions.  It has long been a problem that free functions seem to pollute a shared namespace and there didn’t seem to be a clear way to hide them.
>> 
>> Would a plausible simplification of the proposal be to have it fine-grained enough to address free functions but not methods inside types?
>> Incidentally, although I do not see it in the proposal, I assume that * in some form will be permitted (as in, `import Foundation using *`).
>> 
>> 
>>> I can see the use case for hiding specific symbols when they come into conflict with your own, but in your example you're hiding specific methods declared *in* an imported type. What is the use case here? Is it going to allow me to open backdoors so that, if I don't like `Foo.frobnicate()`, I can hide it and then substitute my own in an extension? This seems like a bad thing at first blush.
>> 
>> For members that would be an acceptable use-case.  The worst-case scenario that comes to mind is this being used as a way to “virtually override” a method in a subclass.  Then again, the scope of the damage is limited to the file in which you’ve declared this monstrosity so clients and even you will not be able to see it outside of there unless you explicitly redeclare the hiding import (in which case, you probably know what you’re doing).
>> 
>> A use care here might be hiding the KVO-ish parts of an object from yourself, or more generally subsetting out the part of an API you know you shouldn’t interact with in a particular submodule.
>> 
>>> 
>>> I can see the obvious use case for renaming modules and types on import--basically, in my mind, it's like typealiases with hiding, and it's available in other languages of course. But how would renaming methods work? If Foo conforms to Equatable and I rename `Foo.==` to `Foo.!=`, is the type I import still Equatable? How would it behave? And even if Foo is fine, what happens if I try to subclass my Frankensteinian Foo?
>> 
>> Of course you still conform to Equatable.  The renaming defines a mapping from your names to “proper" names.  For example, if you use a renaming import to change the requirements of a protocol in a file, then your conformance will simply look at the mapping and see that everything resolves into its proper place.  Bear in mind that your renamings will not survive outside of the file in which you declare them.  Frankenteinian Foo exists where you say it does and nowhere else.  Everybody else just sees Foo conform to Equatable (unless they rename things themselves).
>> 
>> Maybe let's work through an example:
>> 
>> Suppose we have in stdlib:
>> 
>> ```
>> public protocol FooProtocol {
>>   func frobnicate()
>> }
>> ```
>> 
>> Now, I write a library:
>> 
>> ```
>> import Swift.FooProtocol renaming (FooProtocol.frobnicate(), to: FooProtocol.bobnicate())
>> 
>> public open class MyFoo : Swift.FooProtocol {
>>   public open func bobnicate() {
>>     print("Does your head hurt yet?")
>>   }
>> }
>> ```
>> 
>> Now, you are an end user of my sinister library.
>> 
>> What is the public API of `MyFoo`?
> 
> The proposal addresses this
> 
> > 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.
> 
> The user (and even you in other files that import this module) will see a protocol conformance exactly as laid out in the Swift.FooProtocol module.
> 
>> For you, does `MyFoo` conform to `Swift.FooProtocol`?
> 
> It conforms because the renaming you wrote describes a way of resolving FooProtocol.bobnicate() (your API) to FooProtocol.frobnicate() (everybody else’s API).
> 
>> Can you call `MyFoo.frobnicate()`? How about `MyFoo.bobnicate()`?
>> 
>> What if you try to subclass `MyFoo`?
> 
> If you are inside the module you wrote the renaming, you will use it.  If you are outside of it, you will see the protocol requirement sans renaming.
> 
>> Does your subclass still conform to `Swift.FooProtocol`?
>> Do you override `bobnicate()` or `frobnicate()`?
>> My head hurts…
> 
> Because you have explicitly renamed the protocol requirement, you will override the same protocol requirement both inside and outside this module but your renaming will not propagate to other files unless they themselves opt in the way you have here.  It would be particularly sinister if you could arbitrarily edit the user-facing API of members simply by importing a library.
> 
> Sounds good. If I understand you correctly, by conforming `MyFoo` to an internally renamed `Swift.FooProtocol`, the renaming of the user-facing API for `FooProtocol` means that the public API of `MyFoo` is changed so that, outside the module, it has a member `frobnicate()` and no longer has a member `bobnicate()`?
> 
> 
>>  
>> 
>>> 
>>> On Mon, Jul 18, 2016 at 16:10 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 <>) 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.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.<<<<<)
>>>  <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
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
> 

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


More information about the swift-evolution mailing list