> On Feb 21, 2017, at 11:54 PM, Robert Widmann <[email protected]> wrote: > > >> On Feb 22, 2017, at 12:41 AM, Matthew Johnson <[email protected] >> <mailto:[email protected]>> wrote: >> >> >> >> Sent from my iPad >> >> On Feb 21, 2017, at 11:09 PM, Robert Widmann <[email protected] >> <mailto:[email protected]>> wrote: >> >>> >>>> On Feb 21, 2017, at 11:59 PM, Matthew Johnson <[email protected] >>>> <mailto:[email protected]>> wrote: >>>> >>>> >>>>> On Feb 21, 2017, at 10:41 PM, Robert Widmann <[email protected] >>>>> <mailto:[email protected]>> wrote: >>>>> >>>>> By API boundaries I mean both the one internal to MyModule.Foo and the >>>>> one defined by MyModule. Here “the API boundary” is explicitly about the >>>>> submodule MyModule.Foo, whose internal state may have been “unsealed” in >>>>> the top level by the extension, but has not been re-exported. >>>> >>>> I’m sorry, but I just don’t understand how modules form an API boundary in >>>> this system. To me a boundary means something that blocks access. In >>>> this system `internal` ranges over the entire module and all submodules. >>>> The only boundaries I can see besides the module itself are files and >>>> lexical scopes (with `fileprivate` and `private`). >>>> >>> >>> A module is a named region that introduces a lexical scope into which >>> declarations may be nested. The name of the module can be used to access >>> these member declarations. A module, like other aggregate structures in >>> Swift, may be extended with new declarations over one or more translation >>> units (files). >>> >>> >>> Your API boundary lives, as it does today, at the edges of each (sub)module >>> declaration. APIs that are public or open in a module defines code that is >>> free to move across this boundary and into the open. APIs that are >>> internal are free to have their modules unsealed into other internal >>> modules to enable modular composition. APIs that are private and >>> fileprivate do not participate in the API boundary because they are not >>> eligible for any kind of export. >>> >>> If any of that is unclear, please let me know. >> >> Yes, in fact parts are unclear. >> >> "APIs that are public or open in a module defines code that is free to move >> across this boundary and into the open" >> >> This is unclear because you're saying submodules form an API boundary and >> you're also saying we need to make APIs open or public to allow them to move >> across this boundary. But then you say we can unseal it (import or extend, >> right?) within the module and gain visibility to the internal symbols. Are >> you trying to say that it's a soft boundary within the module that can be >> permeated with an import or by extension? > > Of course. Soft implies more permeability than you are actually afforded, > but if you want to think of it that way then it may help to put it in > context. > > For what it’s worth, the bulk of the discussion around this feature is > focused on author-side concerns like the behavior of internal modules because > access control makes a mess of any reasonable semantics. > >> >> If so, that's not the kind of boundary I think many of us are talking about. >> We're talking about a hard boundary within the module, but broader than a >> file. > > How then, does one go about accessing declarations contained in these kinds > of impermeable modules? You must define points of exposure to be able to use > the module. What you’re describing is as though you had can only build > hierarchies of completely private types and then cherry-pick them one-by-one > into the open - which, mind you, is not a pattern encouraged by any of the > access control levels we have today and isn’t supported by any language I’m > aware of.
There are ways to do this without requiring cherry picking individual types. I’m not looking for impermeable modules. I’m looking for bounded visibility within the module in a way that is very similar to `fileprivate`, but at a larger granularity. I’m writing up my view of submodules so we have something more concrete to discuss. > >> >>>> means that it is trivial to put code anywhere within the module that >>>> extends the submodule and wraps a symbol in a new name and declares it >>>> `public`. >>> >>> Precisely. That’s the same pattern that good Swift code, arguably good >>> code in any language that enables hiding, uses today. >>> >>>> They can also trivially add a `public import MyModule.Foo` anywhere at the >>>> top level of their file because every file is forced to include top level >>>> scope. >>> >>> Perhaps you misunderstand. Say the APIs in MyModule.Foo were all of >>> internal or stricter access: The re-export is a no-op. You cannot change >>> the access level of declarations, you can only do the modular thing and >>> wrap them in a palatable interface for export by a module you want to be >>> user-facing. You have to decide to make an API public, just as today you >>> have to decide to make part of an interface public. I don’t see how this >>> is distinct from the goals of this proposal. >> >> Yes, I understand this. But submodules aren't visible outside the module by >> default. It's possible for a submodule to have public and open symbols >> without the top level public import anywhere in the program. > >> What I'm saying here is that someone in a distant part of the code base >> could arbitrarily add it if they decided to. The system doesn't prevent it. >> >> > > Because, by definition, you are not in a “distant part of the codebase” when > you are extending a module. You’re introducing related functionality under > the same namespace with more related functionality. Anything else is > fundamentally anti-modular because it pollutes different concerns together > into an interlocking directorate. Miles away implies cognitive and semantic > distance when you’re probably physically in the same directory! > >> I understand that you consider that a non goal. I'm simply pointing out >> that the system has this property. I think it's reasonable to want a system >> with different properties. And I don't think it's clear yet exactly what >> kind of system might garner the support necessary to be accepted as Swift's >> submodule system. That's part of the reason we have these discussions! :) > > I so appreciate this, too. I genuinely enjoy discussions that try to poke > holes and prod out better explanations. It’s how you iterate on proposals and > just make good things happen in a community like this. > > ~Robert Widmann > >> >>> >>>> >>>> In my opinion, we need to identify what goals we have for a submodule >>>> system - what problems are we trying to solve and what use cases do we >>>> intend to enable. >>>> >>>> There are quite a few of us who want the ability to form solid API >>>> boundaries inside a module and view this as one of the fundamental >>>> features of a submodule system. It’s reasonable to ask why we view this >>>> capability as essential. >>>> >>>> I can’t speak for anyone else, but here are a few reasons why it’s >>>> important to me: >>>> >>>> * Solid API boundaries are essential to good design. >>>> * Having access to an entire code base does not reduce the benefits of #1. >>>> Some code bases are substantial in size and hard boundaries are important >>>> to keeping them manageable. >>>> * Using full-fledged modules to do this is possible, but also involves a >>>> bit of ceremony that is incidental, not essential complexity in many >>>> cases. It would be better to have a lighter weight mechanism to do this. >>>> * Swift currently only has whole module optimization, not whole program >>>> optimization. There is a performance penalty to using full-fledged >>>> modules. >>>> >>>>> >>>>> ~Robert Widmann >>>>> >>>>>> On Feb 21, 2017, at 11:38 PM, Matthew Johnson <[email protected] >>>>>> <mailto:[email protected]>> wrote: >>>>>> >>>>>> >>>>>>> On Feb 21, 2017, at 10:29 PM, Robert Widmann <[email protected] >>>>>>> <mailto:[email protected]>> wrote: >>>>>>> >>>>>>> This level of access, the “private to this submodule except to the >>>>>>> select set of interfaces I want to see it” level, is the equivalent of >>>>>>> friend classes in C++. I don’t consider leaving this out to be a hole, >>>>>>> nor is it an "encapsulation-related problem” because at no point can >>>>>>> you break the API boundary and re-export anything here with a higher >>>>>>> level of access than it had previously. >>>>>> >>>>>> By API boundary you mean the top-level module, right? >>>>>> >>>>>>> >>>>>>>> On Feb 21, 2017, at 11:13 PM, Matthew Johnson <[email protected] >>>>>>>> <mailto:[email protected]>> wrote: >>>>>>>> >>>>>>>> >>>>>>>>> On Feb 21, 2017, at 10:11 PM, Matthew Johnson <[email protected] >>>>>>>>> <mailto:[email protected]>> wrote: >>>>>>>>> >>>>>>>>> >>>>>>>>>> On Feb 21, 2017, at 9:47 PM, Brent Royal-Gordon via swift-evolution >>>>>>>>>> <[email protected] <mailto:[email protected]>> wrote: >>>>>>>>>> >>>>>>>>>>> On Feb 21, 2017, at 7:38 PM, Robert Widmann >>>>>>>>>>> <[email protected] <mailto:[email protected]>> wrote: >>>>>>>>>>> >>>>>>>>>>> Correct. Because, in dividing the submodule across an extension, >>>>>>>>>>> you have placed what should be a private API into a >>>>>>>>>>> differently-scoped location. >>>>>>>>>> >>>>>>>>>> Okay. So is your submodule design not intended to address the "I >>>>>>>>>> want to encapsulate implementation details so they're only visible >>>>>>>>>> to several units of code in different files, but not the entire >>>>>>>>>> module" use case? Because if there's no way to scope a symbol to >>>>>>>>>> "everything inside this submodule, but nothing outside this >>>>>>>>>> submodule", I think it leaves that use case unserved. >>>>>>>>> >>>>>>>>> Unless I’m missing something there is also another >>>>>>>>> encapsulation-related problem with the proposed design. Let’s >>>>>>>>> suppose for the sake of discussion there was a `submoduleprivate` >>>>>>>>> access modifier (intentionally ungainly and not realistic). >>>>>>>>> >>>>>>>>> // File 1 >>>>>>>>> module Foo { >>>>>>>>> // internal, visible to the whole module >>>>>>>>> class Bar { submoduleprivate var protectedState: Int = 0 } >>>>>>>>> } >>>>>>>>> >>>>>>>>> // File 2 - Has nothing to do with Foo at all >>>>>>>>> import MyModule.Foo >>>>>>>>> >>>>>>>>> module NotFoo { >>>>>>>>> // Hey, I need to see Bar.protectedState!!! >>>>>>>>> func totallyNotFoo() { >>>>>>>>> var bar = Bar() >>>>>>>>> bar.foosExposedPrivates = 42 >>>>>>>>> } >>>>>>>>> } >>>>>>>>> >>>>>>>>> // ok, I’ll just add an extension to Foo so I can see >>>>>>>>> submoduleprivate and wrap what I need >>>>>>>>> module Foo { >>>>>>>> >>>>>>>> Oops, this should have been `extension Foo`, but otherwise I believe >>>>>>>> it is valid under this proposal. >>>>>>>> >>>>>>>>> // Hey, I’ll be nice and keep it fileprivate, but I could make it >>>>>>>>> public if I wanted to. >>>>>>>>> extension Foo { >>>>>>>>> fileprivate var foosExposedPrivates: Int { >>>>>>>>> // Yep, I’m inside Foo so I can see it’s submoduleprivate stuff >>>>>>>>> get { return protectedState } >>>>>>>>> set { protectedState = newValue } >>>>>>>>> } >>>>>>>>> } >>>>>>>>> } >>>>>>>>> >>>>>>>>>> >>>>>>>>>> -- >>>>>>>>>> Brent Royal-Gordon >>>>>>>>>> Architechies >>>>>>>>>> >>>>>>>>>> _______________________________________________ >>>>>>>>>> swift-evolution mailing list >>>>>>>>>> [email protected] <mailto:[email protected]> >>>>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>>>>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>>>>>>>> >>>>>>>> >>>>>>> >>>>>> >>>>> >>>> >>> >
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
