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

Xiaodi Wu xiaodi.wu at gmail.com
Mon Jul 18 17:00:57 CDT 2016


On Mon, Jul 18, 2016 at 4:49 PM, Robert Widmann <rwidmann at apple.com> wrote:

>
> On Jul 18, 2016, at 2:32 PM, Xiaodi Wu <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`?
For you, does `MyFoo` conform to `Swift.FooProtocol`?
Can you call `MyFoo.frobnicate()`? How about `MyFoo.bobnicate()`?

What if you try to subclass `MyFoo`?
Does your subclass still conform to `Swift.FooProtocol`?
Do you override `bobnicate()` or `frobnicate()`?
My head hurts...


>
>
> On Mon, Jul 18, 2016 at 16:10 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.
>>
>> 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)
>>
>> 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
>> swift-evolution at swift.org
>> 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/d996af43/attachment.html>


More information about the swift-evolution mailing list