[Draft] Really Simple Submodules


(Michel Fortin) #1

Sorry for introducing yet another submodule proposal out of the blue.

I'm a bit surprised at how far-reaching the various submodule proposals floated on this list have been. Directories, access controls, @exported imports... For comparison's sake here's one that is *really* simple and short I wrote today. Best of all: it requires no new access modifier.

I also expect everyone to scream at it because it does not include all the desirable features of a submodule system. At the very least I'll have redefined the meaning of lightweight in that discussion. Good reading.

Also available here:
https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18

## Motivation

The goal of this proposal is to provide lightweight intra-module boundaries so you can avoid exposing every `internal` declaration within a big module to all other files in the module.

Not a goal: addressing the file-level access fileprivate/private or scoped/protected debates. This is left to other proposals.

## Summary

This proposal adds the declarations `submodule` and `import submodule`. It also limits the visibility of `internal` to files with a matching `submodule` or `import submodule` declaration.

Submodules are never exposed outside of the module. They only change the visibility of internal declarations, so there is no point in exposing them publicly.

Submodules are not bound to directories, nor are they necessarily hierarchical.

This change is purely additive and introduces no source compatibility issue.

## Details

A `submodule <name>` declaration at the beginning of a file contains an identifier with the submodule name:

  submodule Foo

  internal func foo() {}
  public func pub() {}

`internal` declarations within that file are only visible to other files sharing the same submodule name. The submodule only protects `internal` declarations: `public` declarations in the file are visible everywhere (in other submodules and in other modules).

A file can be part of more than one submodule:

  submodule Foo
  submodule Bar

  internal func achoo() {
    foo() // available in Foo (from other file)
  }

This makes the internal `achoo` function visible within both the `Foo` and `Bar` submodules. Also note that it makes internal members of both submodules `Foo` and `Bar` visible within the file.

A file can access internal declarations of a submodule without having to expose its own internal functions to the submodule with `import submodule`:

  submodule Test
  import submodule Foo

  internal func test() {
    foo() // internal, imported from Foo
    achoo() // internal, imported from Foo
    pub() // public, so always visible
  }

Finally, when a file has no submodule declaration, its internal declarations are visible everywhere in the module and all its submodules:

  --- Hello.swift ---
  // no submodule declaration
  internal func hello() {}

  --- World.swift ---
  submodule World
  internal func test() {
    hello() // visible!
  }

## Nitpicky Details (Conflicting Declarations)

Declaring `internal` things that share the same name in two separate submodules is not a conflict:

  --- Foo1.swift ---
  submodule Foo1
  class Foo {} // added to Foo1

  --- Foo2.swift ---
  submodule Foo2
  submodule Foo3
  class Foo {} // added to Foo2 and Foo3

(Note: It would be a conflict if one of them was `public`, because `public` declarations are always visible everywhere inside (and outside of) the module.)

Attempting to use both from another submodule will create ambiguity errors. You can disambiguate using the submodule name as a prefix:

  --- Main.swift ---
  import submodule Foo1
  import submodule Foo2
  import submodule Foo3
  let f0 = Foo() // ambiguity error
  let f1 = Foo1.Foo() // good
  let f2 = Foo2.Foo() // good
  let f3 = Foo3.Foo() // good

Best to avoid this for your own sanity however.

## Alternatives Considered

### Conflicting Declarations

Instead of allowing conflicting symbols in different submodules, we could continue to disallow conflicting `internal` declarations even when they belong to different submodules. This would make the design simpler, as it is closer to how `internal` currently works and prevent ambiguity errors from arising when importing multiple submodules. The behavior would be a little surprising however.

We could also simplify by removing the ability to use the submodule name as a prefix to disambiguate. This has the advantage that if you put a type inside of a submodule with the same name, no conflict can arise between the name of the type and the name of the submodule. Disambiguation would have to be done by renaming one of the conflicting declarations. Since this ambiguity can only affect `internal` declarations (submodules only group internal declarations), requiring a rename will never break any public API. But forcing a rename is not a very elegant solution.

### `import` syntax

Renaming `import submodule` to `import internal`. Having "internal" in the name could make it clearer that we are giving access to internal declarations of the submodule. But it also make the import less relatable to the `submodule` declaration in other files.

## Future Directions

### Submodule-Private

While a submodule-private access modifier could have been added to this proposal, the belief is that this proposal can live without it, and not having this greatly reduce the little details to explore and thus simplifies the design.

In many cases you can work around this by putting "private" stuff in a separate submodule (somewhat similar to private headers in C land). For instance:

  --- Stuff.swift ---
  submodule Stuff
  submdoule StuffImpl

  func pack() { packImpl() }

  --- StuffImpl.swift ---
  submodule StuffImpl

  func packImpl() { ... }

This will not work for stored properties however. A future proposal could suggest allowing stored properties in extensions to help with this.

And a future proposal could also add submodule-private to limit visibility of some declarations to only those files that are part of a specific module.

···

--
Michel Fortin
https://michelf.ca


The One Stop Shop for Previous Submodule Pitches
(Robert Widmann) #2

This proposal strikes me not as a submodules proposal, but as a sneaky way to add 'friend' to Swift. It is conceptually simpler because it doesn't address modularity at all!

~Robert Widmann

2017/03/05 17:16、Michel Fortin via swift-evolution <swift-evolution@swift.org> のメッセージ:

Sorry for introducing yet another submodule proposal out of the blue.

I'm a bit surprised at how far-reaching the various submodule proposals floated on this list have been. Directories, access controls, @exported imports... For comparison's sake here's one that is *really* simple and short I wrote today. Best of all: it requires no new access modifier.

I also expect everyone to scream at it because it does not include all the desirable features of a submodule system. At the very least I'll have redefined the meaning of lightweight in that discussion. Good reading.

Also available here:
https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18

## Motivation

The goal of this proposal is to provide lightweight intra-module boundaries so you can avoid exposing every `internal` declaration within a big module to all other files in the module.

I don't think this was a stated goal of anybody because that’s not the primary issue submodules are trying to solve. Projects with a large amount of internal declarations may be clamoring to be put into multiple (sub)modules, but because the style most have converged on doesn’t usually involve a large amount of free functions, your internal declarations are usually types and as such have members that are already encapsulated.

Not a goal: addressing the file-level access fileprivate/private or scoped/protected debates. This is left to other proposals.

## Summary

This proposal adds the declarations `submodule` and `import submodule`. It also limits the visibility of `internal` to files with a matching `submodule` or `import submodule` declaration.

Submodules are never exposed outside of the module. They only change the visibility of internal declarations, so there is no point in exposing them publicly.

Submodules are not bound to directories, nor are they necessarily hierarchical.

This change is purely additive and introduces no source compatibility issue.

This particular set of changes is, but the `import submodule` change is source-breaking in that we already have syntax for this.

## Details

A `submodule <name>` declaration at the beginning of a file contains an identifier with the submodule name:

  submodule Foo

  internal func foo() {}
  public func pub() {}

`internal` declarations within that file are only visible to other files sharing the same submodule name. The submodule only protects `internal` declarations: `public` declarations in the file are visible everywhere (in other submodules and in other modules).

This definition of public is anti-modular. It seems as though this style of submodule will encourage people to split their definitions across multiple files (good) then provide a pile of submodule-membership definitions to intertwine them with each other as they need access to each other’s symbols (bad). A module is a self-contained entity by definition. Dependencies should generally be on further child (sub)modules - vertically, not horizontally.

···

A file can be part of more than one submodule:

  submodule Foo
  submodule Bar

  internal func achoo() {
      foo() // available in Foo (from other file)
  }

This makes the internal `achoo` function visible within both the `Foo` and `Bar` submodules. Also note that it makes internal members of both submodules `Foo` and `Bar` visible within the file.

A file can access internal declarations of a submodule without having to expose its own internal functions to the submodule with `import submodule`:

  submodule Test
  import submodule Foo

  internal func test() {
      foo() // internal, imported from Foo
      achoo() // internal, imported from Foo
      pub() // public, so always visible
  }

Finally, when a file has no submodule declaration, its internal declarations are visible everywhere in the module and all its submodules:

  --- Hello.swift ---
  // no submodule declaration
  internal func hello() {}

  --- World.swift ---
  submodule World
  internal func test() {
      hello() // visible!
  }

## Nitpicky Details (Conflicting Declarations)

Declaring `internal` things that share the same name in two separate submodules is not a conflict:

  --- Foo1.swift ---
  submodule Foo1
  class Foo {} // added to Foo1

  --- Foo2.swift ---
  submodule Foo2
  submodule Foo3
  class Foo {} // added to Foo2 and Foo3

(Note: It would be a conflict if one of them was `public`, because `public` declarations are always visible everywhere inside (and outside of) the module.)

Attempting to use both from another submodule will create ambiguity errors. You can disambiguate using the submodule name as a prefix:

  --- Main.swift ---
  import submodule Foo1
  import submodule Foo2
  import submodule Foo3
  let f0 = Foo() // ambiguity error
  let f1 = Foo1.Foo() // good
  let f2 = Foo2.Foo() // good
  let f3 = Foo3.Foo() // good

Best to avoid this for your own sanity however.

## Alternatives Considered

### Conflicting Declarations

Instead of allowing conflicting symbols in different submodules, we could continue to disallow conflicting `internal` declarations even when they belong to different submodules. This would make the design simpler, as it is closer to how `internal` currently works and prevent ambiguity errors from arising when importing multiple submodules. The behavior would be a little surprising however.

We could also simplify by removing the ability to use the submodule name as a prefix to disambiguate. This has the advantage that if you put a type inside of a submodule with the same name, no conflict can arise between the name of the type and the name of the submodule. Disambiguation would have to be done by renaming one of the conflicting declarations. Since this ambiguity can only affect `internal` declarations (submodules only group internal declarations), requiring a rename will never break any public API. But forcing a rename is not a very elegant solution.

### `import` syntax

Renaming `import submodule` to `import internal`. Having "internal" in the name could make it clearer that we are giving access to internal declarations of the submodule. But it also make the import less relatable to the `submodule` declaration in other files.

## Future Directions

### Submodule-Private

While a submodule-private access modifier could have been added to this proposal, the belief is that this proposal can live without it, and not having this greatly reduce the little details to explore and thus simplifies the design.

In many cases you can work around this by putting "private" stuff in a separate submodule (somewhat similar to private headers in C land). For instance:

  --- Stuff.swift ---
  submodule Stuff
  submdoule StuffImpl

  func pack() { packImpl() }

  --- StuffImpl.swift ---
  submodule StuffImpl

  func packImpl() { ... }

This will not work for stored properties however. A future proposal could suggest allowing stored properties in extensions to help with this.

And a future proposal could also add submodule-private to limit visibility of some declarations to only those files that are part of a specific module.

--
Michel Fortin
https://michelf.ca

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Rien) #3

+1 for the simplicity. I like that.

However I dislike the non-hierachical approach: It should not be possible for an ‘outsider’ to declare itself part of a submodule if the original developer of that submodule did not want that. I understand that the submodules are not exported through the module (though wait for the requests to do so if this were ever implemented) but if large teams want to use submodules to structure their code base, some level of hierarchy is imo necessary.

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Balancingrock
Project: http://swiftfire.nl

···

On 05 Mar 2017, at 23:16, Michel Fortin via swift-evolution <swift-evolution@swift.org> wrote:

Sorry for introducing yet another submodule proposal out of the blue.

I'm a bit surprised at how far-reaching the various submodule proposals floated on this list have been. Directories, access controls, @exported imports... For comparison's sake here's one that is *really* simple and short I wrote today. Best of all: it requires no new access modifier.

I also expect everyone to scream at it because it does not include all the desirable features of a submodule system. At the very least I'll have redefined the meaning of lightweight in that discussion. Good reading.

Also available here:
https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18

## Motivation

The goal of this proposal is to provide lightweight intra-module boundaries so you can avoid exposing every `internal` declaration within a big module to all other files in the module.

Not a goal: addressing the file-level access fileprivate/private or scoped/protected debates. This is left to other proposals.

## Summary

This proposal adds the declarations `submodule` and `import submodule`. It also limits the visibility of `internal` to files with a matching `submodule` or `import submodule` declaration.

Submodules are never exposed outside of the module. They only change the visibility of internal declarations, so there is no point in exposing them publicly.

Submodules are not bound to directories, nor are they necessarily hierarchical.

This change is purely additive and introduces no source compatibility issue.

## Details

A `submodule <name>` declaration at the beginning of a file contains an identifier with the submodule name:

  submodule Foo

  internal func foo() {}
  public func pub() {}

`internal` declarations within that file are only visible to other files sharing the same submodule name. The submodule only protects `internal` declarations: `public` declarations in the file are visible everywhere (in other submodules and in other modules).

A file can be part of more than one submodule:

  submodule Foo
  submodule Bar

  internal func achoo() {
    foo() // available in Foo (from other file)
  }

This makes the internal `achoo` function visible within both the `Foo` and `Bar` submodules. Also note that it makes internal members of both submodules `Foo` and `Bar` visible within the file.

A file can access internal declarations of a submodule without having to expose its own internal functions to the submodule with `import submodule`:

  submodule Test
  import submodule Foo

  internal func test() {
    foo() // internal, imported from Foo
    achoo() // internal, imported from Foo
    pub() // public, so always visible
  }

Finally, when a file has no submodule declaration, its internal declarations are visible everywhere in the module and all its submodules:

  --- Hello.swift ---
  // no submodule declaration
  internal func hello() {}

  --- World.swift ---
  submodule World
  internal func test() {
    hello() // visible!
  }

## Nitpicky Details (Conflicting Declarations)

Declaring `internal` things that share the same name in two separate submodules is not a conflict:

  --- Foo1.swift ---
  submodule Foo1
  class Foo {} // added to Foo1

  --- Foo2.swift ---
  submodule Foo2
  submodule Foo3
  class Foo {} // added to Foo2 and Foo3

(Note: It would be a conflict if one of them was `public`, because `public` declarations are always visible everywhere inside (and outside of) the module.)

Attempting to use both from another submodule will create ambiguity errors. You can disambiguate using the submodule name as a prefix:

  --- Main.swift ---
  import submodule Foo1
  import submodule Foo2
  import submodule Foo3
  let f0 = Foo() // ambiguity error
  let f1 = Foo1.Foo() // good
  let f2 = Foo2.Foo() // good
  let f3 = Foo3.Foo() // good

Best to avoid this for your own sanity however.

## Alternatives Considered

### Conflicting Declarations

Instead of allowing conflicting symbols in different submodules, we could continue to disallow conflicting `internal` declarations even when they belong to different submodules. This would make the design simpler, as it is closer to how `internal` currently works and prevent ambiguity errors from arising when importing multiple submodules. The behavior would be a little surprising however.

We could also simplify by removing the ability to use the submodule name as a prefix to disambiguate. This has the advantage that if you put a type inside of a submodule with the same name, no conflict can arise between the name of the type and the name of the submodule. Disambiguation would have to be done by renaming one of the conflicting declarations. Since this ambiguity can only affect `internal` declarations (submodules only group internal declarations), requiring a rename will never break any public API. But forcing a rename is not a very elegant solution.

### `import` syntax

Renaming `import submodule` to `import internal`. Having "internal" in the name could make it clearer that we are giving access to internal declarations of the submodule. But it also make the import less relatable to the `submodule` declaration in other files.

## Future Directions

### Submodule-Private

While a submodule-private access modifier could have been added to this proposal, the belief is that this proposal can live without it, and not having this greatly reduce the little details to explore and thus simplifies the design.

In many cases you can work around this by putting "private" stuff in a separate submodule (somewhat similar to private headers in C land). For instance:

  --- Stuff.swift ---
  submodule Stuff
  submdoule StuffImpl

  func pack() { packImpl() }

  --- StuffImpl.swift ---
  submodule StuffImpl

  func packImpl() { ... }

This will not work for stored properties however. A future proposal could suggest allowing stored properties in extensions to help with this.

And a future proposal could also add submodule-private to limit visibility of some declarations to only those files that are part of a specific module.

--
Michel Fortin
https://michelf.ca

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Michel Fortin) #4

This proposal strikes me not as a submodules proposal, but as a sneaky way to add 'friend' to Swift. It is conceptually simpler because it doesn't address modularity at all!

~Robert Widmann

2017/03/05 17:16、Michel Fortin via swift-evolution <swift-evolution@swift.org> のメッセージ:

Sorry for introducing yet another submodule proposal out of the blue.

I'm a bit surprised at how far-reaching the various submodule proposals floated on this list have been. Directories, access controls, @exported imports... For comparison's sake here's one that is *really* simple and short I wrote today. Best of all: it requires no new access modifier.

I also expect everyone to scream at it because it does not include all the desirable features of a submodule system. At the very least I'll have redefined the meaning of lightweight in that discussion. Good reading.

Also available here:
https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18

## Motivation

The goal of this proposal is to provide lightweight intra-module boundaries so you can avoid exposing every `internal` declaration within a big module to all other files in the module.

I don't think this was a stated goal of anybody because that’s not the primary issue submodules are trying to solve. Projects with a large amount of internal declarations may be clamoring to be put into multiple (sub)modules, but because the style most have converged on doesn’t usually involve a large amount of free functions, your internal declarations are usually types and as such have members that are already encapsulated.

Well maybe that's just a problem specific to me, but that's the goal I'd like submodule to solve. If I have four intermingled classes, I'd like to restrict the interminglement to those four classes. My current options for that are a) to create a separate module, b) to put them all in the same file with `fileprivate`, or c) tolerate that the rest of the module will see everything all the time with `internal`. I'm choosing (c) at the present time because (a) is too much work and (b) is more messy.

You are absolutely right in your observation that it's some sort of "friend". But then the same observation could apply to the `internal` members in a module or the `fileprivate` members in a file. The delimiting boundaries are different, but the concept is the same. What I want is the ability to better define those boundaries.

Not a goal: addressing the file-level access fileprivate/private or scoped/protected debates. This is left to other proposals.

## Summary

This proposal adds the declarations `submodule` and `import submodule`. It also limits the visibility of `internal` to files with a matching `submodule` or `import submodule` declaration.

Submodules are never exposed outside of the module. They only change the visibility of internal declarations, so there is no point in exposing them publicly.

Submodules are not bound to directories, nor are they necessarily hierarchical.

This change is purely additive and introduces no source compatibility issue.

This particular set of changes is, but the `import submodule` change is source-breaking in that we already have syntax for this.

Good catch. If you have a module called "submodule" and you import it, that'll break.

## Details

A `submodule <name>` declaration at the beginning of a file contains an identifier with the submodule name:

submodule Foo

internal func foo() {}
public func pub() {}

`internal` declarations within that file are only visible to other files sharing the same submodule name. The submodule only protects `internal` declarations: `public` declarations in the file are visible everywhere (in other submodules and in other modules).

This definition of public is anti-modular. It seems as though this style of submodule will encourage people to split their definitions across multiple files (good) then provide a pile of submodule-membership definitions to intertwine them with each other as they need access to each other’s symbols (bad). A module is a self-contained entity by definition. Dependencies should generally be on further child (sub)modules - vertically, not horizontally.

I'll say that this definition of `public` is quite handy as long as the submodules are implementing public-facing functionality. I agree it is less useful when the submodule is the backend of some other component in the module. You are thus right in that it naturally organizes things horizontally.

So if you want to use submodules to organize self-contained dependencies, my approach does not work very well. In particular, there is no containment of anything `internal` when you `import submodule Foo`. That would be improved if we had submodule-private as discussed in Future Directions... with the caveat that it's still easy to access any submodule's internals by declaring any file to be part of the submodule. And also you want a strict hierarchy, which this proposal completely eschew.

We obviously have a different philosophy on how strict and hierarchic the submodules should be. I say the strictness should stay at the module boundaries. If you want enforced self-contained modules, change those submodules into full modules. If you want to ship the whole thing as a single library, then we need to adapt the tooling so you can merge privately those modules inside of the main library. (Ideally this would be done with a single compiler invocation, allowing full cross-module optimization.) This is a tooling and packaging problem that does not need to leak into a plethora of new language concepts. At least, this is how I see it.

···

On 5 mars 2017, at 22:28, Robert Widmann <devteam.codafi@gmail.com> wrote:

A file can be part of more than one submodule:

submodule Foo
submodule Bar

internal func achoo() {
     foo() // available in Foo (from other file)
}

This makes the internal `achoo` function visible within both the `Foo` and `Bar` submodules. Also note that it makes internal members of both submodules `Foo` and `Bar` visible within the file.

A file can access internal declarations of a submodule without having to expose its own internal functions to the submodule with `import submodule`:

submodule Test
import submodule Foo

internal func test() {
     foo() // internal, imported from Foo
     achoo() // internal, imported from Foo
     pub() // public, so always visible
}

Finally, when a file has no submodule declaration, its internal declarations are visible everywhere in the module and all its submodules:

--- Hello.swift ---
// no submodule declaration
internal func hello() {}

--- World.swift ---
submodule World
internal func test() {
     hello() // visible!
}

## Nitpicky Details (Conflicting Declarations)

Declaring `internal` things that share the same name in two separate submodules is not a conflict:

--- Foo1.swift ---
submodule Foo1
class Foo {} // added to Foo1

--- Foo2.swift ---
submodule Foo2
submodule Foo3
class Foo {} // added to Foo2 and Foo3

(Note: It would be a conflict if one of them was `public`, because `public` declarations are always visible everywhere inside (and outside of) the module.)

Attempting to use both from another submodule will create ambiguity errors. You can disambiguate using the submodule name as a prefix:

--- Main.swift ---
import submodule Foo1
import submodule Foo2
import submodule Foo3
let f0 = Foo() // ambiguity error
let f1 = Foo1.Foo() // good
let f2 = Foo2.Foo() // good
let f3 = Foo3.Foo() // good

Best to avoid this for your own sanity however.

## Alternatives Considered

### Conflicting Declarations

Instead of allowing conflicting symbols in different submodules, we could continue to disallow conflicting `internal` declarations even when they belong to different submodules. This would make the design simpler, as it is closer to how `internal` currently works and prevent ambiguity errors from arising when importing multiple submodules. The behavior would be a little surprising however.

We could also simplify by removing the ability to use the submodule name as a prefix to disambiguate. This has the advantage that if you put a type inside of a submodule with the same name, no conflict can arise between the name of the type and the name of the submodule. Disambiguation would have to be done by renaming one of the conflicting declarations. Since this ambiguity can only affect `internal` declarations (submodules only group internal declarations), requiring a rename will never break any public API. But forcing a rename is not a very elegant solution.

### `import` syntax

Renaming `import submodule` to `import internal`. Having "internal" in the name could make it clearer that we are giving access to internal declarations of the submodule. But it also make the import less relatable to the `submodule` declaration in other files.

## Future Directions

### Submodule-Private

While a submodule-private access modifier could have been added to this proposal, the belief is that this proposal can live without it, and not having this greatly reduce the little details to explore and thus simplifies the design.

In many cases you can work around this by putting "private" stuff in a separate submodule (somewhat similar to private headers in C land). For instance:

--- Stuff.swift ---
submodule Stuff
submdoule StuffImpl

func pack() { packImpl() }

--- StuffImpl.swift ---
submodule StuffImpl

func packImpl() { ... }

This will not work for stored properties however. A future proposal could suggest allowing stored properties in extensions to help with this.

And a future proposal could also add submodule-private to limit visibility of some declarations to only those files that are part of a specific module.

--
Michel Fortin
https://michelf.ca

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Michel Fortin
https://michelf.ca


(Michel Fortin) #5

How do you define 'outsider'? What is the threat model you want to be protected from?

I believe it is someone on your team or a contributor not following the established policies for the project, in particular the policies related to encapsulation. Those policies are necessarily going to vary from project to project and from team to team. It is to accommodate these varied cases and policies that the other submodule proposals have plenty of different ways submodules can be organized and used, and also why they are complex.

Perhaps instead of baking those policies as language features the team should use a pre-commit linter tool enforcing them. This proposal should make it simpler to write such a linter.

···

On 6 mars 2017, at 2:47, Rien <Rien@balancingrock.nl> wrote:

+1 for the simplicity. I like that.

However I dislike the non-hierachical approach: It should not be possible for an ‘outsider’ to declare itself part of a submodule if the original developer of that submodule did not want that. I understand that the submodules are not exported through the module (though wait for the requests to do so if this were ever implemented) but if large teams want to use submodules to structure their code base, some level of hierarchy is imo necessary.

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Balancingrock
Project: http://swiftfire.nl

On 05 Mar 2017, at 23:16, Michel Fortin via swift-evolution <swift-evolution@swift.org> wrote:

Sorry for introducing yet another submodule proposal out of the blue.

I'm a bit surprised at how far-reaching the various submodule proposals floated on this list have been. Directories, access controls, @exported imports... For comparison's sake here's one that is *really* simple and short I wrote today. Best of all: it requires no new access modifier.

I also expect everyone to scream at it because it does not include all the desirable features of a submodule system. At the very least I'll have redefined the meaning of lightweight in that discussion. Good reading.

Also available here:
https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18

## Motivation

The goal of this proposal is to provide lightweight intra-module boundaries so you can avoid exposing every `internal` declaration within a big module to all other files in the module.

Not a goal: addressing the file-level access fileprivate/private or scoped/protected debates. This is left to other proposals.

## Summary

This proposal adds the declarations `submodule` and `import submodule`. It also limits the visibility of `internal` to files with a matching `submodule` or `import submodule` declaration.

Submodules are never exposed outside of the module. They only change the visibility of internal declarations, so there is no point in exposing them publicly.

Submodules are not bound to directories, nor are they necessarily hierarchical.

This change is purely additive and introduces no source compatibility issue.

## Details

A `submodule <name>` declaration at the beginning of a file contains an identifier with the submodule name:

  submodule Foo

  internal func foo() {}
  public func pub() {}

`internal` declarations within that file are only visible to other files sharing the same submodule name. The submodule only protects `internal` declarations: `public` declarations in the file are visible everywhere (in other submodules and in other modules).

A file can be part of more than one submodule:

  submodule Foo
  submodule Bar

  internal func achoo() {
    foo() // available in Foo (from other file)
  }

This makes the internal `achoo` function visible within both the `Foo` and `Bar` submodules. Also note that it makes internal members of both submodules `Foo` and `Bar` visible within the file.

A file can access internal declarations of a submodule without having to expose its own internal functions to the submodule with `import submodule`:

  submodule Test
  import submodule Foo

  internal func test() {
    foo() // internal, imported from Foo
    achoo() // internal, imported from Foo
    pub() // public, so always visible
  }

Finally, when a file has no submodule declaration, its internal declarations are visible everywhere in the module and all its submodules:

  --- Hello.swift ---
  // no submodule declaration
  internal func hello() {}

  --- World.swift ---
  submodule World
  internal func test() {
    hello() // visible!
  }

## Nitpicky Details (Conflicting Declarations)

Declaring `internal` things that share the same name in two separate submodules is not a conflict:

  --- Foo1.swift ---
  submodule Foo1
  class Foo {} // added to Foo1

  --- Foo2.swift ---
  submodule Foo2
  submodule Foo3
  class Foo {} // added to Foo2 and Foo3

(Note: It would be a conflict if one of them was `public`, because `public` declarations are always visible everywhere inside (and outside of) the module.)

Attempting to use both from another submodule will create ambiguity errors. You can disambiguate using the submodule name as a prefix:

  --- Main.swift ---
  import submodule Foo1
  import submodule Foo2
  import submodule Foo3
  let f0 = Foo() // ambiguity error
  let f1 = Foo1.Foo() // good
  let f2 = Foo2.Foo() // good
  let f3 = Foo3.Foo() // good

Best to avoid this for your own sanity however.

## Alternatives Considered

### Conflicting Declarations

Instead of allowing conflicting symbols in different submodules, we could continue to disallow conflicting `internal` declarations even when they belong to different submodules. This would make the design simpler, as it is closer to how `internal` currently works and prevent ambiguity errors from arising when importing multiple submodules. The behavior would be a little surprising however.

We could also simplify by removing the ability to use the submodule name as a prefix to disambiguate. This has the advantage that if you put a type inside of a submodule with the same name, no conflict can arise between the name of the type and the name of the submodule. Disambiguation would have to be done by renaming one of the conflicting declarations. Since this ambiguity can only affect `internal` declarations (submodules only group internal declarations), requiring a rename will never break any public API. But forcing a rename is not a very elegant solution.

### `import` syntax

Renaming `import submodule` to `import internal`. Having "internal" in the name could make it clearer that we are giving access to internal declarations of the submodule. But it also make the import less relatable to the `submodule` declaration in other files.

## Future Directions

### Submodule-Private

While a submodule-private access modifier could have been added to this proposal, the belief is that this proposal can live without it, and not having this greatly reduce the little details to explore and thus simplifies the design.

In many cases you can work around this by putting "private" stuff in a separate submodule (somewhat similar to private headers in C land). For instance:

  --- Stuff.swift ---
  submodule Stuff
  submdoule StuffImpl

  func pack() { packImpl() }

  --- StuffImpl.swift ---
  submodule StuffImpl

  func packImpl() { ... }

This will not work for stored properties however. A future proposal could suggest allowing stored properties in extensions to help with this.

And a future proposal could also add submodule-private to limit visibility of some declarations to only those files that are part of a specific module.

--
Michel Fortin
https://michelf.ca

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Michel Fortin
https://michelf.ca


(Robert Widmann) #6

If this were a problem that could be fixed by tooling, you'd be submitting this proposal against the package manager.

It's important to note I'm approaching this problem from the principles that underpin modular programming practices in other languages, it is not merely "philosophical". A proposal that claims to support submodules should come with some consistent support for modularity or rebrand to more honestly reflect its contents. As it stands, this proposal effectively introduces another level of access (while paradoxically claiming to avoid access control discussions), and suggests more access control to support its contents later. Maybe discussion should start there instead of with "submodules".

~Robert Widmann

2017/03/06 0:39、Michel Fortin <michel.fortin@michelf.ca> のメッセージ:

···

On 5 mars 2017, at 22:28, Robert Widmann <devteam.codafi@gmail.com> wrote:

This proposal strikes me not as a submodules proposal, but as a sneaky way to add 'friend' to Swift. It is conceptually simpler because it doesn't address modularity at all!

~Robert Widmann

2017/03/05 17:16、Michel Fortin via swift-evolution <swift-evolution@swift.org> のメッセージ:

Sorry for introducing yet another submodule proposal out of the blue.

I'm a bit surprised at how far-reaching the various submodule proposals floated on this list have been. Directories, access controls, @exported imports... For comparison's sake here's one that is *really* simple and short I wrote today. Best of all: it requires no new access modifier.

I also expect everyone to scream at it because it does not include all the desirable features of a submodule system. At the very least I'll have redefined the meaning of lightweight in that discussion. Good reading.

Also available here:
https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18

## Motivation

The goal of this proposal is to provide lightweight intra-module boundaries so you can avoid exposing every `internal` declaration within a big module to all other files in the module.

I don't think this was a stated goal of anybody because that’s not the primary issue submodules are trying to solve. Projects with a large amount of internal declarations may be clamoring to be put into multiple (sub)modules, but because the style most have converged on doesn’t usually involve a large amount of free functions, your internal declarations are usually types and as such have members that are already encapsulated.

Well maybe that's just a problem specific to me, but that's the goal I'd like submodule to solve. If I have four intermingled classes, I'd like to restrict the interminglement to those four classes. My current options for that are a) to create a separate module, b) to put them all in the same file with `fileprivate`, or c) tolerate that the rest of the module will see everything all the time with `internal`. I'm choosing (c) at the present time because (a) is too much work and (b) is more messy.

You are absolutely right in your observation that it's some sort of "friend". But then the same observation could apply to the `internal` members in a module or the `fileprivate` members in a file. The delimiting boundaries are different, but the concept is the same. What I want is the ability to better define those boundaries.

Not a goal: addressing the file-level access fileprivate/private or scoped/protected debates. This is left to other proposals.

## Summary

This proposal adds the declarations `submodule` and `import submodule`. It also limits the visibility of `internal` to files with a matching `submodule` or `import submodule` declaration.

Submodules are never exposed outside of the module. They only change the visibility of internal declarations, so there is no point in exposing them publicly.

Submodules are not bound to directories, nor are they necessarily hierarchical.

This change is purely additive and introduces no source compatibility issue.

This particular set of changes is, but the `import submodule` change is source-breaking in that we already have syntax for this.

Good catch. If you have a module called "submodule" and you import it, that'll break.

## Details

A `submodule <name>` declaration at the beginning of a file contains an identifier with the submodule name:

submodule Foo

internal func foo() {}
public func pub() {}

`internal` declarations within that file are only visible to other files sharing the same submodule name. The submodule only protects `internal` declarations: `public` declarations in the file are visible everywhere (in other submodules and in other modules).

This definition of public is anti-modular. It seems as though this style of submodule will encourage people to split their definitions across multiple files (good) then provide a pile of submodule-membership definitions to intertwine them with each other as they need access to each other’s symbols (bad). A module is a self-contained entity by definition. Dependencies should generally be on further child (sub)modules - vertically, not horizontally.

I'll say that this definition of `public` is quite handy as long as the submodules are implementing public-facing functionality. I agree it is less useful when the submodule is the backend of some other component in the module. You are thus right in that it naturally organizes things horizontally.

So if you want to use submodules to organize self-contained dependencies, my approach does not work very well. In particular, there is no containment of anything `internal` when you `import submodule Foo`. That would be improved if we had submodule-private as discussed in Future Directions... with the caveat that it's still easy to access any submodule's internals by declaring any file to be part of the submodule. And also you want a strict hierarchy, which this proposal completely eschew.

We obviously have a different philosophy on how strict and hierarchic the submodules should be. I say the strictness should stay at the module boundaries. If you want enforced self-contained modules, change those submodules into full modules. If you want to ship the whole thing as a single library, then we need to adapt the tooling so you can merge privately those modules inside of the main library. (Ideally this would be done with a single compiler invocation, allowing full cross-module optimization.) This is a tooling and packaging problem that does not need to leak into a plethora of new language concepts. At least, this is how I see it.

A file can be part of more than one submodule:

submodule Foo
submodule Bar

internal func achoo() {
     foo() // available in Foo (from other file)
}

This makes the internal `achoo` function visible within both the `Foo` and `Bar` submodules. Also note that it makes internal members of both submodules `Foo` and `Bar` visible within the file.

A file can access internal declarations of a submodule without having to expose its own internal functions to the submodule with `import submodule`:

submodule Test
import submodule Foo

internal func test() {
     foo() // internal, imported from Foo
     achoo() // internal, imported from Foo
     pub() // public, so always visible
}

Finally, when a file has no submodule declaration, its internal declarations are visible everywhere in the module and all its submodules:

--- Hello.swift ---
// no submodule declaration
internal func hello() {}

--- World.swift ---
submodule World
internal func test() {
     hello() // visible!
}

## Nitpicky Details (Conflicting Declarations)

Declaring `internal` things that share the same name in two separate submodules is not a conflict:

--- Foo1.swift ---
submodule Foo1
class Foo {} // added to Foo1

--- Foo2.swift ---
submodule Foo2
submodule Foo3
class Foo {} // added to Foo2 and Foo3

(Note: It would be a conflict if one of them was `public`, because `public` declarations are always visible everywhere inside (and outside of) the module.)

Attempting to use both from another submodule will create ambiguity errors. You can disambiguate using the submodule name as a prefix:

--- Main.swift ---
import submodule Foo1
import submodule Foo2
import submodule Foo3
let f0 = Foo() // ambiguity error
let f1 = Foo1.Foo() // good
let f2 = Foo2.Foo() // good
let f3 = Foo3.Foo() // good

Best to avoid this for your own sanity however.

## Alternatives Considered

### Conflicting Declarations

Instead of allowing conflicting symbols in different submodules, we could continue to disallow conflicting `internal` declarations even when they belong to different submodules. This would make the design simpler, as it is closer to how `internal` currently works and prevent ambiguity errors from arising when importing multiple submodules. The behavior would be a little surprising however.

We could also simplify by removing the ability to use the submodule name as a prefix to disambiguate. This has the advantage that if you put a type inside of a submodule with the same name, no conflict can arise between the name of the type and the name of the submodule. Disambiguation would have to be done by renaming one of the conflicting declarations. Since this ambiguity can only affect `internal` declarations (submodules only group internal declarations), requiring a rename will never break any public API. But forcing a rename is not a very elegant solution.

### `import` syntax

Renaming `import submodule` to `import internal`. Having "internal" in the name could make it clearer that we are giving access to internal declarations of the submodule. But it also make the import less relatable to the `submodule` declaration in other files.

## Future Directions

### Submodule-Private

While a submodule-private access modifier could have been added to this proposal, the belief is that this proposal can live without it, and not having this greatly reduce the little details to explore and thus simplifies the design.

In many cases you can work around this by putting "private" stuff in a separate submodule (somewhat similar to private headers in C land). For instance:

--- Stuff.swift ---
submodule Stuff
submdoule StuffImpl

func pack() { packImpl() }

--- StuffImpl.swift ---
submodule StuffImpl

func packImpl() { ... }

This will not work for stored properties however. A future proposal could suggest allowing stored properties in extensions to help with this.

And a future proposal could also add submodule-private to limit visibility of some declarations to only those files that are part of a specific module.

--
Michel Fortin
https://michelf.ca

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Michel Fortin
https://michelf.ca


(Matthew Johnson) #7

If this were a problem that could be fixed by tooling, you'd be submitting this proposal against the package manager.

It's important to note I'm approaching this problem from the principles that underpin modular programming practices in other languages, it is not merely "philosophical". A proposal that claims to support submodules should come with some consistent support for modularity or rebrand to more honestly reflect its contents.

Robert, you have said several times that ideas suggested by others are not modular and have referenced other languages in the process. Yet (as far as I know) you haven't actually defined what you believe "modular" means or referenced other languages that you believe provide modularity in the way you are looking for. Perhaps it would be useful if you provide or reference a definition and mention some specific languages you believe provide good modularity.

I believe your proposal mentions the ML module system as specifically not providing the kind of modularity you are looking for. That makes sense because Swift's protocols already offer similar features. But that doesn't offer much insight into what you specifically mean by modularity or what other languages inspired that understanding.

I hope you are willing to elaborate further on this.

···

Sent from my iPad

On Mar 6, 2017, at 1:39 AM, Robert Widmann via swift-evolution <swift-evolution@swift.org> wrote:

As it stands, this proposal effectively introduces another level of access (while paradoxically claiming to avoid access control discussions), and suggests more access control to support its contents later. Maybe discussion should start there instead of with "submodules".

~Robert Widmann

2017/03/06 0:39、Michel Fortin <michel.fortin@michelf.ca> のメッセージ:

On 5 mars 2017, at 22:28, Robert Widmann <devteam.codafi@gmail.com> wrote:

This proposal strikes me not as a submodules proposal, but as a sneaky way to add 'friend' to Swift. It is conceptually simpler because it doesn't address modularity at all!

~Robert Widmann

2017/03/05 17:16、Michel Fortin via swift-evolution <swift-evolution@swift.org> のメッセージ:

Sorry for introducing yet another submodule proposal out of the blue.

I'm a bit surprised at how far-reaching the various submodule proposals floated on this list have been. Directories, access controls, @exported imports... For comparison's sake here's one that is *really* simple and short I wrote today. Best of all: it requires no new access modifier.

I also expect everyone to scream at it because it does not include all the desirable features of a submodule system. At the very least I'll have redefined the meaning of lightweight in that discussion. Good reading.

Also available here:
https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18

## Motivation

The goal of this proposal is to provide lightweight intra-module boundaries so you can avoid exposing every `internal` declaration within a big module to all other files in the module.

I don't think this was a stated goal of anybody because that’s not the primary issue submodules are trying to solve. Projects with a large amount of internal declarations may be clamoring to be put into multiple (sub)modules, but because the style most have converged on doesn’t usually involve a large amount of free functions, your internal declarations are usually types and as such have members that are already encapsulated.

Well maybe that's just a problem specific to me, but that's the goal I'd like submodule to solve. If I have four intermingled classes, I'd like to restrict the interminglement to those four classes. My current options for that are a) to create a separate module, b) to put them all in the same file with `fileprivate`, or c) tolerate that the rest of the module will see everything all the time with `internal`. I'm choosing (c) at the present time because (a) is too much work and (b) is more messy.

You are absolutely right in your observation that it's some sort of "friend". But then the same observation could apply to the `internal` members in a module or the `fileprivate` members in a file. The delimiting boundaries are different, but the concept is the same. What I want is the ability to better define those boundaries.

Not a goal: addressing the file-level access fileprivate/private or scoped/protected debates. This is left to other proposals.

## Summary

This proposal adds the declarations `submodule` and `import submodule`. It also limits the visibility of `internal` to files with a matching `submodule` or `import submodule` declaration.

Submodules are never exposed outside of the module. They only change the visibility of internal declarations, so there is no point in exposing them publicly.

Submodules are not bound to directories, nor are they necessarily hierarchical.

This change is purely additive and introduces no source compatibility issue.

This particular set of changes is, but the `import submodule` change is source-breaking in that we already have syntax for this.

Good catch. If you have a module called "submodule" and you import it, that'll break.

## Details

A `submodule <name>` declaration at the beginning of a file contains an identifier with the submodule name:

submodule Foo

internal func foo() {}
public func pub() {}

`internal` declarations within that file are only visible to other files sharing the same submodule name. The submodule only protects `internal` declarations: `public` declarations in the file are visible everywhere (in other submodules and in other modules).

This definition of public is anti-modular. It seems as though this style of submodule will encourage people to split their definitions across multiple files (good) then provide a pile of submodule-membership definitions to intertwine them with each other as they need access to each other’s symbols (bad). A module is a self-contained entity by definition. Dependencies should generally be on further child (sub)modules - vertically, not horizontally.

I'll say that this definition of `public` is quite handy as long as the submodules are implementing public-facing functionality. I agree it is less useful when the submodule is the backend of some other component in the module. You are thus right in that it naturally organizes things horizontally.

So if you want to use submodules to organize self-contained dependencies, my approach does not work very well. In particular, there is no containment of anything `internal` when you `import submodule Foo`. That would be improved if we had submodule-private as discussed in Future Directions... with the caveat that it's still easy to access any submodule's internals by declaring any file to be part of the submodule. And also you want a strict hierarchy, which this proposal completely eschew.

We obviously have a different philosophy on how strict and hierarchic the submodules should be. I say the strictness should stay at the module boundaries. If you want enforced self-contained modules, change those submodules into full modules. If you want to ship the whole thing as a single library, then we need to adapt the tooling so you can merge privately those modules inside of the main library. (Ideally this would be done with a single compiler invocation, allowing full cross-module optimization.) This is a tooling and packaging problem that does not need to leak into a plethora of new language concepts. At least, this is how I see it.

A file can be part of more than one submodule:

submodule Foo
submodule Bar

internal func achoo() {
     foo() // available in Foo (from other file)
}

This makes the internal `achoo` function visible within both the `Foo` and `Bar` submodules. Also note that it makes internal members of both submodules `Foo` and `Bar` visible within the file.

A file can access internal declarations of a submodule without having to expose its own internal functions to the submodule with `import submodule`:

submodule Test
import submodule Foo

internal func test() {
     foo() // internal, imported from Foo
     achoo() // internal, imported from Foo
     pub() // public, so always visible
}

Finally, when a file has no submodule declaration, its internal declarations are visible everywhere in the module and all its submodules:

--- Hello.swift ---
// no submodule declaration
internal func hello() {}

--- World.swift ---
submodule World
internal func test() {
     hello() // visible!
}

## Nitpicky Details (Conflicting Declarations)

Declaring `internal` things that share the same name in two separate submodules is not a conflict:

--- Foo1.swift ---
submodule Foo1
class Foo {} // added to Foo1

--- Foo2.swift ---
submodule Foo2
submodule Foo3
class Foo {} // added to Foo2 and Foo3

(Note: It would be a conflict if one of them was `public`, because `public` declarations are always visible everywhere inside (and outside of) the module.)

Attempting to use both from another submodule will create ambiguity errors. You can disambiguate using the submodule name as a prefix:

--- Main.swift ---
import submodule Foo1
import submodule Foo2
import submodule Foo3
let f0 = Foo() // ambiguity error
let f1 = Foo1.Foo() // good
let f2 = Foo2.Foo() // good
let f3 = Foo3.Foo() // good

Best to avoid this for your own sanity however.

## Alternatives Considered

### Conflicting Declarations

Instead of allowing conflicting symbols in different submodules, we could continue to disallow conflicting `internal` declarations even when they belong to different submodules. This would make the design simpler, as it is closer to how `internal` currently works and prevent ambiguity errors from arising when importing multiple submodules. The behavior would be a little surprising however.

We could also simplify by removing the ability to use the submodule name as a prefix to disambiguate. This has the advantage that if you put a type inside of a submodule with the same name, no conflict can arise between the name of the type and the name of the submodule. Disambiguation would have to be done by renaming one of the conflicting declarations. Since this ambiguity can only affect `internal` declarations (submodules only group internal declarations), requiring a rename will never break any public API. But forcing a rename is not a very elegant solution.

### `import` syntax

Renaming `import submodule` to `import internal`. Having "internal" in the name could make it clearer that we are giving access to internal declarations of the submodule. But it also make the import less relatable to the `submodule` declaration in other files.

## Future Directions

### Submodule-Private

While a submodule-private access modifier could have been added to this proposal, the belief is that this proposal can live without it, and not having this greatly reduce the little details to explore and thus simplifies the design.

In many cases you can work around this by putting "private" stuff in a separate submodule (somewhat similar to private headers in C land). For instance:

--- Stuff.swift ---
submodule Stuff
submdoule StuffImpl

func pack() { packImpl() }

--- StuffImpl.swift ---
submodule StuffImpl

func packImpl() { ... }

This will not work for stored properties however. A future proposal could suggest allowing stored properties in extensions to help with this.

And a future proposal could also add submodule-private to limit visibility of some declarations to only those files that are part of a specific module.

--
Michel Fortin
https://michelf.ca

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Michel Fortin
https://michelf.ca

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Michel Fortin) #8

I believe `submodule` in my proposal introduces a segmented-scope-like thing, akin to how the various files included in a module are combined as one collection of declarations. And you have the ability to import things from that scope with `import submodule`, like with modules. I think the name submodule fits as some sort of "lesser module", but if someone has a better name for this I'll listen.

We could add a submodule-private access level, but I'm not sure it is worth its weight. There's plenty of possibilities, but I know I wouldn't be too happy with more access levels than what we have now. For each access level you add the more fine-grained they become and the more difficult it is to decide which one is the right one. Hence why I'm keeping the idea of adding access levels as a separate concern worth of its own debate. This proposal is solely focused on providing intra-module boundaries, and I think those boundaries can work without introducing a new access level.

···

On 6 mars 2017, at 2:39, Robert Widmann <devteam.codafi@gmail.com> wrote:

If this were a problem that could be fixed by tooling, you'd be submitting this proposal against the package manager.

It's important to note I'm approaching this problem from the principles that underpin modular programming practices in other languages, it is not merely "philosophical". A proposal that claims to support submodules should come with some consistent support for modularity or rebrand to more honestly reflect its contents. As it stands, this proposal effectively introduces another level of access (while paradoxically claiming to avoid access control discussions), and suggests more access control to support its contents later. Maybe discussion should start there instead of with "submodules".

~Robert Widmann

2017/03/06 0:39、Michel Fortin <michel.fortin@michelf.ca <mailto:michel.fortin@michelf.ca>> のメッセージ:

On 5 mars 2017, at 22:28, Robert Widmann <devteam.codafi@gmail.com <mailto:devteam.codafi@gmail.com>> wrote:

This proposal strikes me not as a submodules proposal, but as a sneaky way to add 'friend' to Swift. It is conceptually simpler because it doesn't address modularity at all!

~Robert Widmann

2017/03/05 17:16、Michel Fortin via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> のメッセージ:

Sorry for introducing yet another submodule proposal out of the blue.

I'm a bit surprised at how far-reaching the various submodule proposals floated on this list have been. Directories, access controls, @exported imports... For comparison's sake here's one that is *really* simple and short I wrote today. Best of all: it requires no new access modifier.

I also expect everyone to scream at it because it does not include all the desirable features of a submodule system. At the very least I'll have redefined the meaning of lightweight in that discussion. Good reading.

Also available here:
https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18

## Motivation

The goal of this proposal is to provide lightweight intra-module boundaries so you can avoid exposing every `internal` declaration within a big module to all other files in the module.

I don't think this was a stated goal of anybody because that’s not the primary issue submodules are trying to solve. Projects with a large amount of internal declarations may be clamoring to be put into multiple (sub)modules, but because the style most have converged on doesn’t usually involve a large amount of free functions, your internal declarations are usually types and as such have members that are already encapsulated.

Well maybe that's just a problem specific to me, but that's the goal I'd like submodule to solve. If I have four intermingled classes, I'd like to restrict the interminglement to those four classes. My current options for that are a) to create a separate module, b) to put them all in the same file with `fileprivate`, or c) tolerate that the rest of the module will see everything all the time with `internal`. I'm choosing (c) at the present time because (a) is too much work and (b) is more messy.

You are absolutely right in your observation that it's some sort of "friend". But then the same observation could apply to the `internal` members in a module or the `fileprivate` members in a file. The delimiting boundaries are different, but the concept is the same. What I want is the ability to better define those boundaries.

Not a goal: addressing the file-level access fileprivate/private or scoped/protected debates. This is left to other proposals.

## Summary

This proposal adds the declarations `submodule` and `import submodule`. It also limits the visibility of `internal` to files with a matching `submodule` or `import submodule` declaration.

Submodules are never exposed outside of the module. They only change the visibility of internal declarations, so there is no point in exposing them publicly.

Submodules are not bound to directories, nor are they necessarily hierarchical.

This change is purely additive and introduces no source compatibility issue.

This particular set of changes is, but the `import submodule` change is source-breaking in that we already have syntax for this.

Good catch. If you have a module called "submodule" and you import it, that'll break.

## Details

A `submodule <name>` declaration at the beginning of a file contains an identifier with the submodule name:

submodule Foo

internal func foo() {}
public func pub() {}

`internal` declarations within that file are only visible to other files sharing the same submodule name. The submodule only protects `internal` declarations: `public` declarations in the file are visible everywhere (in other submodules and in other modules).

This definition of public is anti-modular. It seems as though this style of submodule will encourage people to split their definitions across multiple files (good) then provide a pile of submodule-membership definitions to intertwine them with each other as they need access to each other’s symbols (bad). A module is a self-contained entity by definition. Dependencies should generally be on further child (sub)modules - vertically, not horizontally.

I'll say that this definition of `public` is quite handy as long as the submodules are implementing public-facing functionality. I agree it is less useful when the submodule is the backend of some other component in the module. You are thus right in that it naturally organizes things horizontally.

So if you want to use submodules to organize self-contained dependencies, my approach does not work very well. In particular, there is no containment of anything `internal` when you `import submodule Foo`. That would be improved if we had submodule-private as discussed in Future Directions... with the caveat that it's still easy to access any submodule's internals by declaring any file to be part of the submodule. And also you want a strict hierarchy, which this proposal completely eschew.

We obviously have a different philosophy on how strict and hierarchic the submodules should be. I say the strictness should stay at the module boundaries. If you want enforced self-contained modules, change those submodules into full modules. If you want to ship the whole thing as a single library, then we need to adapt the tooling so you can merge privately those modules inside of the main library. (Ideally this would be done with a single compiler invocation, allowing full cross-module optimization.) This is a tooling and packaging problem that does not need to leak into a plethora of new language concepts. At least, this is how I see it.

A file can be part of more than one submodule:

submodule Foo
submodule Bar

internal func achoo() {
     foo() // available in Foo (from other file)
}

This makes the internal `achoo` function visible within both the `Foo` and `Bar` submodules. Also note that it makes internal members of both submodules `Foo` and `Bar` visible within the file.

A file can access internal declarations of a submodule without having to expose its own internal functions to the submodule with `import submodule`:

submodule Test
import submodule Foo

internal func test() {
     foo() // internal, imported from Foo
     achoo() // internal, imported from Foo
     pub() // public, so always visible
}

Finally, when a file has no submodule declaration, its internal declarations are visible everywhere in the module and all its submodules:

--- Hello.swift ---
// no submodule declaration
internal func hello() {}

--- World.swift ---
submodule World
internal func test() {
     hello() // visible!
}

## Nitpicky Details (Conflicting Declarations)

Declaring `internal` things that share the same name in two separate submodules is not a conflict:

--- Foo1.swift ---
submodule Foo1
class Foo {} // added to Foo1

--- Foo2.swift ---
submodule Foo2
submodule Foo3
class Foo {} // added to Foo2 and Foo3

(Note: It would be a conflict if one of them was `public`, because `public` declarations are always visible everywhere inside (and outside of) the module.)

Attempting to use both from another submodule will create ambiguity errors. You can disambiguate using the submodule name as a prefix:

--- Main.swift ---
import submodule Foo1
import submodule Foo2
import submodule Foo3
let f0 = Foo() // ambiguity error
let f1 = Foo1.Foo() // good
let f2 = Foo2.Foo() // good
let f3 = Foo3.Foo() // good

Best to avoid this for your own sanity however.

## Alternatives Considered

### Conflicting Declarations

Instead of allowing conflicting symbols in different submodules, we could continue to disallow conflicting `internal` declarations even when they belong to different submodules. This would make the design simpler, as it is closer to how `internal` currently works and prevent ambiguity errors from arising when importing multiple submodules. The behavior would be a little surprising however.

We could also simplify by removing the ability to use the submodule name as a prefix to disambiguate. This has the advantage that if you put a type inside of a submodule with the same name, no conflict can arise between the name of the type and the name of the submodule. Disambiguation would have to be done by renaming one of the conflicting declarations. Since this ambiguity can only affect `internal` declarations (submodules only group internal declarations), requiring a rename will never break any public API. But forcing a rename is not a very elegant solution.

### `import` syntax

Renaming `import submodule` to `import internal`. Having "internal" in the name could make it clearer that we are giving access to internal declarations of the submodule. But it also make the import less relatable to the `submodule` declaration in other files.

## Future Directions

### Submodule-Private

While a submodule-private access modifier could have been added to this proposal, the belief is that this proposal can live without it, and not having this greatly reduce the little details to explore and thus simplifies the design.

In many cases you can work around this by putting "private" stuff in a separate submodule (somewhat similar to private headers in C land). For instance:

--- Stuff.swift ---
submodule Stuff
submdoule StuffImpl

func pack() { packImpl() }

--- StuffImpl.swift ---
submodule StuffImpl

func packImpl() { ... }

This will not work for stored properties however. A future proposal could suggest allowing stored properties in extensions to help with this.

And a future proposal could also add submodule-private to limit visibility of some declarations to only those files that are part of a specific module.

--
Michel Fortin
https://michelf.ca <https://michelf.ca/>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Michel Fortin
https://michelf.ca <https://michelf.ca/>

--
Michel Fortin
https://michelf.ca


(Robert Widmann) #9

My proposal doesn't mention ML, though it may have come up during our discussions.

I do remember, however, defining modularity many times though indirectly. Let me be clear: A modular system is one in which substitution is the foremost goal. An API boundary may be specified in the form of a set of types, a set of free functions, a set of protocols, whatever, but the implementation of these are freely substitutable by a different module implementing its innards completely differently but implementing the same interface outwardly. In fact, the formal definition for this in Type Theory is the Principle of Substitution extended to support structures.

ML carries this principle to its logical conclusions by allowing programs to be structured in terms of interfaces so abstract even the underlying type definitions are often hidden from you. This allows programs to be built with a high degree of genericity and by definition a high degree of reusability. Swift, obviously, has not agreed that that level of abstraction is useful and so we cannot wholesale import ML modules into the language - nor would we want to as it immediately runs afoul of the “how Swift-y is this addition” question. However, there are many ML-like and ML-inspired languages that have toed this line before. Assemblies in the Microsoft world, packages in the Java and Go worlds, and Ruby and Python offer the weakest adherence to this definition, with Rust, Clojure, Erlang rounding out the middle, and OCaml and Agda et al. trying to aim closest.

My proposal was built not on top of these, but on top of the existing infrastructure and featureset LLVM modules offer us because I felt they offered a reasonable degree of modularity given the system we already have, and have been battle-tested.

~Robert Widmann

2017/03/06 8:31、Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> のメッセージ:

···

Sent from my iPad

On Mar 6, 2017, at 1:39 AM, Robert Widmann via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

If this were a problem that could be fixed by tooling, you'd be submitting this proposal against the package manager.

It's important to note I'm approaching this problem from the principles that underpin modular programming practices in other languages, it is not merely "philosophical". A proposal that claims to support submodules should come with some consistent support for modularity or rebrand to more honestly reflect its contents.

Robert, you have said several times that ideas suggested by others are not modular and have referenced other languages in the process. Yet (as far as I know) you haven't actually defined what you believe "modular" means or referenced other languages that you believe provide modularity in the way you are looking for. Perhaps it would be useful if you provide or reference a definition and mention some specific languages you believe provide good modularity.

I believe your proposal mentions the ML module system as specifically not providing the kind of modularity you are looking for. That makes sense because Swift's protocols already offer similar features. But that doesn't offer much insight into what you specifically mean by modularity or what other languages inspired that understanding.

I hope you are willing to elaborate further on this.

As it stands, this proposal effectively introduces another level of access (while paradoxically claiming to avoid access control discussions), and suggests more access control to support its contents later. Maybe discussion should start there instead of with "submodules".

~Robert Widmann

2017/03/06 0:39、Michel Fortin <michel.fortin@michelf.ca <mailto:michel.fortin@michelf.ca>> のメッセージ:

On 5 mars 2017, at 22:28, Robert Widmann <devteam.codafi@gmail.com <mailto:devteam.codafi@gmail.com>> wrote:

This proposal strikes me not as a submodules proposal, but as a sneaky way to add 'friend' to Swift. It is conceptually simpler because it doesn't address modularity at all!

~Robert Widmann

2017/03/05 17:16、Michel Fortin via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> のメッセージ:

Sorry for introducing yet another submodule proposal out of the blue.

I'm a bit surprised at how far-reaching the various submodule proposals floated on this list have been. Directories, access controls, @exported imports... For comparison's sake here's one that is *really* simple and short I wrote today. Best of all: it requires no new access modifier.

I also expect everyone to scream at it because it does not include all the desirable features of a submodule system. At the very least I'll have redefined the meaning of lightweight in that discussion. Good reading.

Also available here:
https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18

## Motivation

The goal of this proposal is to provide lightweight intra-module boundaries so you can avoid exposing every `internal` declaration within a big module to all other files in the module.

I don't think this was a stated goal of anybody because that’s not the primary issue submodules are trying to solve. Projects with a large amount of internal declarations may be clamoring to be put into multiple (sub)modules, but because the style most have converged on doesn’t usually involve a large amount of free functions, your internal declarations are usually types and as such have members that are already encapsulated.

Well maybe that's just a problem specific to me, but that's the goal I'd like submodule to solve. If I have four intermingled classes, I'd like to restrict the interminglement to those four classes. My current options for that are a) to create a separate module, b) to put them all in the same file with `fileprivate`, or c) tolerate that the rest of the module will see everything all the time with `internal`. I'm choosing (c) at the present time because (a) is too much work and (b) is more messy.

You are absolutely right in your observation that it's some sort of "friend". But then the same observation could apply to the `internal` members in a module or the `fileprivate` members in a file. The delimiting boundaries are different, but the concept is the same. What I want is the ability to better define those boundaries.

Not a goal: addressing the file-level access fileprivate/private or scoped/protected debates. This is left to other proposals.

## Summary

This proposal adds the declarations `submodule` and `import submodule`. It also limits the visibility of `internal` to files with a matching `submodule` or `import submodule` declaration.

Submodules are never exposed outside of the module. They only change the visibility of internal declarations, so there is no point in exposing them publicly.

Submodules are not bound to directories, nor are they necessarily hierarchical.

This change is purely additive and introduces no source compatibility issue.

This particular set of changes is, but the `import submodule` change is source-breaking in that we already have syntax for this.

Good catch. If you have a module called "submodule" and you import it, that'll break.

## Details

A `submodule <name>` declaration at the beginning of a file contains an identifier with the submodule name:

submodule Foo

internal func foo() {}
public func pub() {}

`internal` declarations within that file are only visible to other files sharing the same submodule name. The submodule only protects `internal` declarations: `public` declarations in the file are visible everywhere (in other submodules and in other modules).

This definition of public is anti-modular. It seems as though this style of submodule will encourage people to split their definitions across multiple files (good) then provide a pile of submodule-membership definitions to intertwine them with each other as they need access to each other’s symbols (bad). A module is a self-contained entity by definition. Dependencies should generally be on further child (sub)modules - vertically, not horizontally.

I'll say that this definition of `public` is quite handy as long as the submodules are implementing public-facing functionality. I agree it is less useful when the submodule is the backend of some other component in the module. You are thus right in that it naturally organizes things horizontally.

So if you want to use submodules to organize self-contained dependencies, my approach does not work very well. In particular, there is no containment of anything `internal` when you `import submodule Foo`. That would be improved if we had submodule-private as discussed in Future Directions... with the caveat that it's still easy to access any submodule's internals by declaring any file to be part of the submodule. And also you want a strict hierarchy, which this proposal completely eschew.

We obviously have a different philosophy on how strict and hierarchic the submodules should be. I say the strictness should stay at the module boundaries. If you want enforced self-contained modules, change those submodules into full modules. If you want to ship the whole thing as a single library, then we need to adapt the tooling so you can merge privately those modules inside of the main library. (Ideally this would be done with a single compiler invocation, allowing full cross-module optimization.) This is a tooling and packaging problem that does not need to leak into a plethora of new language concepts. At least, this is how I see it.

A file can be part of more than one submodule:

submodule Foo
submodule Bar

internal func achoo() {
     foo() // available in Foo (from other file)
}

This makes the internal `achoo` function visible within both the `Foo` and `Bar` submodules. Also note that it makes internal members of both submodules `Foo` and `Bar` visible within the file.

A file can access internal declarations of a submodule without having to expose its own internal functions to the submodule with `import submodule`:

submodule Test
import submodule Foo

internal func test() {
     foo() // internal, imported from Foo
     achoo() // internal, imported from Foo
     pub() // public, so always visible
}

Finally, when a file has no submodule declaration, its internal declarations are visible everywhere in the module and all its submodules:

--- Hello.swift ---
// no submodule declaration
internal func hello() {}

--- World.swift ---
submodule World
internal func test() {
     hello() // visible!
}

## Nitpicky Details (Conflicting Declarations)

Declaring `internal` things that share the same name in two separate submodules is not a conflict:

--- Foo1.swift ---
submodule Foo1
class Foo {} // added to Foo1

--- Foo2.swift ---
submodule Foo2
submodule Foo3
class Foo {} // added to Foo2 and Foo3

(Note: It would be a conflict if one of them was `public`, because `public` declarations are always visible everywhere inside (and outside of) the module.)

Attempting to use both from another submodule will create ambiguity errors. You can disambiguate using the submodule name as a prefix:

--- Main.swift ---
import submodule Foo1
import submodule Foo2
import submodule Foo3
let f0 = Foo() // ambiguity error
let f1 = Foo1.Foo() // good
let f2 = Foo2.Foo() // good
let f3 = Foo3.Foo() // good

Best to avoid this for your own sanity however.

## Alternatives Considered

### Conflicting Declarations

Instead of allowing conflicting symbols in different submodules, we could continue to disallow conflicting `internal` declarations even when they belong to different submodules. This would make the design simpler, as it is closer to how `internal` currently works and prevent ambiguity errors from arising when importing multiple submodules. The behavior would be a little surprising however.

We could also simplify by removing the ability to use the submodule name as a prefix to disambiguate. This has the advantage that if you put a type inside of a submodule with the same name, no conflict can arise between the name of the type and the name of the submodule. Disambiguation would have to be done by renaming one of the conflicting declarations. Since this ambiguity can only affect `internal` declarations (submodules only group internal declarations), requiring a rename will never break any public API. But forcing a rename is not a very elegant solution.

### `import` syntax

Renaming `import submodule` to `import internal`. Having "internal" in the name could make it clearer that we are giving access to internal declarations of the submodule. But it also make the import less relatable to the `submodule` declaration in other files.

## Future Directions

### Submodule-Private

While a submodule-private access modifier could have been added to this proposal, the belief is that this proposal can live without it, and not having this greatly reduce the little details to explore and thus simplifies the design.

In many cases you can work around this by putting "private" stuff in a separate submodule (somewhat similar to private headers in C land). For instance:

--- Stuff.swift ---
submodule Stuff
submdoule StuffImpl

func pack() { packImpl() }

--- StuffImpl.swift ---
submodule StuffImpl

func packImpl() { ... }

This will not work for stored properties however. A future proposal could suggest allowing stored properties in extensions to help with this.

And a future proposal could also add submodule-private to limit visibility of some declarations to only those files that are part of a specific module.

--
Michel Fortin
https://michelf.ca <https://michelf.ca/>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Michel Fortin
https://michelf.ca <https://michelf.ca/>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Robert Widmann) #10

I believe `submodule` in my proposal introduces a segmented-scope-like thing, akin to how the various files included in a module are combined as one collection of declarations. And you have the ability to import things from that scope with `import submodule`, like with modules. I think the name submodule fits as some sort of "lesser module", but if someone has a better name for this I'll listen.

My beef is not with that, my beef is with the idea that `public` should leak APIs across module boundaries by default and that a module is merely a way to section off internal declarations. Matthew has given these a name in his scoped access proposal, and Swift today gives the kind of scope you’re trying to define a name - it’s called fileprivate.

···

On Mar 6, 2017, at 10:22 AM, Michel Fortin <michel.fortin@michelf.ca> wrote:

We could add a submodule-private access level, but I'm not sure it is worth its weight. There's plenty of possibilities, but I know I wouldn't be too happy with more access levels than what we have now. For each access level you add the more fine-grained they become and the more difficult it is to decide which one is the right one. Hence why I'm keeping the idea of adding access levels as a separate concern worth of its own debate. This proposal is solely focused on providing intra-module boundaries, and I think those boundaries can work without introducing a new access level.

On 6 mars 2017, at 2:39, Robert Widmann <devteam.codafi@gmail.com <mailto:devteam.codafi@gmail.com>> wrote:

If this were a problem that could be fixed by tooling, you'd be submitting this proposal against the package manager.

It's important to note I'm approaching this problem from the principles that underpin modular programming practices in other languages, it is not merely "philosophical". A proposal that claims to support submodules should come with some consistent support for modularity or rebrand to more honestly reflect its contents. As it stands, this proposal effectively introduces another level of access (while paradoxically claiming to avoid access control discussions), and suggests more access control to support its contents later. Maybe discussion should start there instead of with "submodules".

~Robert Widmann

2017/03/06 0:39、Michel Fortin <michel.fortin@michelf.ca <mailto:michel.fortin@michelf.ca>> のメッセージ:

On 5 mars 2017, at 22:28, Robert Widmann <devteam.codafi@gmail.com <mailto:devteam.codafi@gmail.com>> wrote:

This proposal strikes me not as a submodules proposal, but as a sneaky way to add 'friend' to Swift. It is conceptually simpler because it doesn't address modularity at all!

~Robert Widmann

2017/03/05 17:16、Michel Fortin via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> のメッセージ:

Sorry for introducing yet another submodule proposal out of the blue.

I'm a bit surprised at how far-reaching the various submodule proposals floated on this list have been. Directories, access controls, @exported imports... For comparison's sake here's one that is *really* simple and short I wrote today. Best of all: it requires no new access modifier.

I also expect everyone to scream at it because it does not include all the desirable features of a submodule system. At the very least I'll have redefined the meaning of lightweight in that discussion. Good reading.

Also available here:
https://gist.github.com/michelf/779b1bc26a778051b6231b5639665e18

## Motivation

The goal of this proposal is to provide lightweight intra-module boundaries so you can avoid exposing every `internal` declaration within a big module to all other files in the module.

I don't think this was a stated goal of anybody because that’s not the primary issue submodules are trying to solve. Projects with a large amount of internal declarations may be clamoring to be put into multiple (sub)modules, but because the style most have converged on doesn’t usually involve a large amount of free functions, your internal declarations are usually types and as such have members that are already encapsulated.

Well maybe that's just a problem specific to me, but that's the goal I'd like submodule to solve. If I have four intermingled classes, I'd like to restrict the interminglement to those four classes. My current options for that are a) to create a separate module, b) to put them all in the same file with `fileprivate`, or c) tolerate that the rest of the module will see everything all the time with `internal`. I'm choosing (c) at the present time because (a) is too much work and (b) is more messy.

You are absolutely right in your observation that it's some sort of "friend". But then the same observation could apply to the `internal` members in a module or the `fileprivate` members in a file. The delimiting boundaries are different, but the concept is the same. What I want is the ability to better define those boundaries.

Not a goal: addressing the file-level access fileprivate/private or scoped/protected debates. This is left to other proposals.

## Summary

This proposal adds the declarations `submodule` and `import submodule`. It also limits the visibility of `internal` to files with a matching `submodule` or `import submodule` declaration.

Submodules are never exposed outside of the module. They only change the visibility of internal declarations, so there is no point in exposing them publicly.

Submodules are not bound to directories, nor are they necessarily hierarchical.

This change is purely additive and introduces no source compatibility issue.

This particular set of changes is, but the `import submodule` change is source-breaking in that we already have syntax for this.

Good catch. If you have a module called "submodule" and you import it, that'll break.

## Details

A `submodule <name>` declaration at the beginning of a file contains an identifier with the submodule name:

submodule Foo

internal func foo() {}
public func pub() {}

`internal` declarations within that file are only visible to other files sharing the same submodule name. The submodule only protects `internal` declarations: `public` declarations in the file are visible everywhere (in other submodules and in other modules).

This definition of public is anti-modular. It seems as though this style of submodule will encourage people to split their definitions across multiple files (good) then provide a pile of submodule-membership definitions to intertwine them with each other as they need access to each other’s symbols (bad). A module is a self-contained entity by definition. Dependencies should generally be on further child (sub)modules - vertically, not horizontally.

I'll say that this definition of `public` is quite handy as long as the submodules are implementing public-facing functionality. I agree it is less useful when the submodule is the backend of some other component in the module. You are thus right in that it naturally organizes things horizontally.

So if you want to use submodules to organize self-contained dependencies, my approach does not work very well. In particular, there is no containment of anything `internal` when you `import submodule Foo`. That would be improved if we had submodule-private as discussed in Future Directions... with the caveat that it's still easy to access any submodule's internals by declaring any file to be part of the submodule. And also you want a strict hierarchy, which this proposal completely eschew.

We obviously have a different philosophy on how strict and hierarchic the submodules should be. I say the strictness should stay at the module boundaries. If you want enforced self-contained modules, change those submodules into full modules. If you want to ship the whole thing as a single library, then we need to adapt the tooling so you can merge privately those modules inside of the main library. (Ideally this would be done with a single compiler invocation, allowing full cross-module optimization.) This is a tooling and packaging problem that does not need to leak into a plethora of new language concepts. At least, this is how I see it.

A file can be part of more than one submodule:

submodule Foo
submodule Bar

internal func achoo() {
     foo() // available in Foo (from other file)
}

This makes the internal `achoo` function visible within both the `Foo` and `Bar` submodules. Also note that it makes internal members of both submodules `Foo` and `Bar` visible within the file.

A file can access internal declarations of a submodule without having to expose its own internal functions to the submodule with `import submodule`:

submodule Test
import submodule Foo

internal func test() {
     foo() // internal, imported from Foo
     achoo() // internal, imported from Foo
     pub() // public, so always visible
}

Finally, when a file has no submodule declaration, its internal declarations are visible everywhere in the module and all its submodules:

--- Hello.swift ---
// no submodule declaration
internal func hello() {}

--- World.swift ---
submodule World
internal func test() {
     hello() // visible!
}

## Nitpicky Details (Conflicting Declarations)

Declaring `internal` things that share the same name in two separate submodules is not a conflict:

--- Foo1.swift ---
submodule Foo1
class Foo {} // added to Foo1

--- Foo2.swift ---
submodule Foo2
submodule Foo3
class Foo {} // added to Foo2 and Foo3

(Note: It would be a conflict if one of them was `public`, because `public` declarations are always visible everywhere inside (and outside of) the module.)

Attempting to use both from another submodule will create ambiguity errors. You can disambiguate using the submodule name as a prefix:

--- Main.swift ---
import submodule Foo1
import submodule Foo2
import submodule Foo3
let f0 = Foo() // ambiguity error
let f1 = Foo1.Foo() // good
let f2 = Foo2.Foo() // good
let f3 = Foo3.Foo() // good

Best to avoid this for your own sanity however.

## Alternatives Considered

### Conflicting Declarations

Instead of allowing conflicting symbols in different submodules, we could continue to disallow conflicting `internal` declarations even when they belong to different submodules. This would make the design simpler, as it is closer to how `internal` currently works and prevent ambiguity errors from arising when importing multiple submodules. The behavior would be a little surprising however.

We could also simplify by removing the ability to use the submodule name as a prefix to disambiguate. This has the advantage that if you put a type inside of a submodule with the same name, no conflict can arise between the name of the type and the name of the submodule. Disambiguation would have to be done by renaming one of the conflicting declarations. Since this ambiguity can only affect `internal` declarations (submodules only group internal declarations), requiring a rename will never break any public API. But forcing a rename is not a very elegant solution.

### `import` syntax

Renaming `import submodule` to `import internal`. Having "internal" in the name could make it clearer that we are giving access to internal declarations of the submodule. But it also make the import less relatable to the `submodule` declaration in other files.

## Future Directions

### Submodule-Private

While a submodule-private access modifier could have been added to this proposal, the belief is that this proposal can live without it, and not having this greatly reduce the little details to explore and thus simplifies the design.

In many cases you can work around this by putting "private" stuff in a separate submodule (somewhat similar to private headers in C land). For instance:

--- Stuff.swift ---
submodule Stuff
submdoule StuffImpl

func pack() { packImpl() }

--- StuffImpl.swift ---
submodule StuffImpl

func packImpl() { ... }

This will not work for stored properties however. A future proposal could suggest allowing stored properties in extensions to help with this.

And a future proposal could also add submodule-private to limit visibility of some declarations to only those files that are part of a specific module.

--
Michel Fortin
https://michelf.ca <https://michelf.ca/>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Michel Fortin
https://michelf.ca <https://michelf.ca/>

--
Michel Fortin
https://michelf.ca <https://michelf.ca/>