[swift-evolution] [Review] SE-0159: Fix Private Access Levels

Matthew Johnson matthew at anandabits.com
Sun Mar 26 22:04:49 CDT 2017



Sent from my iPad

> On Mar 26, 2017, at 9:44 PM, Matthew Johnson <matthew at anandabits.com> wrote:
> 
> 
> 
> Sent from my iPad
> 
>> On Mar 26, 2017, at 8:44 PM, Brent Royal-Gordon <brent at architechies.com> wrote:
>> 
>>> On Mar 26, 2017, at 10:43 AM, Matthew Johnson via swift-evolution <swift-evolution at swift.org> wrote:
>>> 
>>>> On Mar 26, 2017, at 10:43 AM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>> 
>>>> This reaches the crux of the issue, doesn't it? The Swift 2 design for access modifiers does exactly what you reject: that design is based on the premise that it is wise for file organization to follow encapsulation.
>>> 
>>> It's always a gamble speculating about the premise of someone else's design.  That said, if I had to guess I would imagine maybe it had as much to do with the legacy of C-family languages and header files as anything else.
>> 
>> 
>> I've seen people say this several times in access control threads, but personally, I don't think the use of files for access control is a "legacy" or a coincidence. I think it's a pretty smart move.
>> 
>> By its nature, the `internal` scope is already defined by files: There is a certain set of files which are compiled together to form the module, and those files all see the same `internal` scope. Unless we entirely abandon the idea of Swift as a fully textual language and move to a FoxPro-style system where code is stored in a structured database, this will always be true in Swift. I have a hard time imagining an environment for writing Swift code where there *aren't* files or some equivalent named-chunk-of-arbitrary-code unit of organization like the "pages" in a playground.
>> 
>> Using files to scope `private` is thus a natural extension of the `internal` scope's rules (one group of files defines the `internal` scope; one single file defines the `private` scope). It also happens to fit well with typical programmer behavior. Organizing your code into files by concern is generally considered a best practice, but files do not carry any other semantic meaning: there's nothing (else) in the language that forces you to put a given declaration in one file or another. So with file-based `private`, there are only two reasons to care about which file a piece of code is in—`private` access and subjective code organization—and these two things are usually aligned.
> 
> They're usually aligned at one level of granularity but often not at another.  Small helper types are subjectively best placed in the same file as the type they are assisting yet there are still good reasons to encapsulate their state.  Further, they should not be visible beyond the code they are helping.  
> 
> Even if you can properly encapsulate everything as desired using separate files and submodules you are left with code organization that subjectively and pragmatically is suboptimal.  I don't want a bunch of files with 20 line helper types in my project.  This leads to way too much file switching which significantly reduces clarity. 

I should have also mentioned that helper types are not the only use case for scoped access.  I often use `private` to hide encapsulates state of a type from much of the implementation of the type itself.  The state and basis methods that operate on it are placed in the type declaration.  Other methods which are defined in terms of the basis methods are placed in extensions.  The basis methods may or may not be visible outside the file.

All of the concerns relevant for helper types are relevant for this use case.  It is a very useful way to organize code.  It would be unfortunate to see the language lose the excellent support it currently has for this style of programming.

> 
>> 
>> This point is actually implicitly acknowledged by, for instance, the submodule proposal Jonathan Hull put forth upthread, where each file is implicitly its own submodule if not declared to be part of a different one. Files aren't always the right encapsulation boundary, but they often are.
> 
> I agree that files are often the right boundary for encapsulation and have not argued against allowing us to address file scopes (unless of course we get a submodule solution that makes them redundant).  I also agree that using groups of files to organize code into submodules makes a lot of sense.
> 
> My point is simply that there is nothing inevitable about using them as an access control boundary.  There are tradeoffs involved.  Having files be the largest addressable scope smaller than the module is largely responsible for the long file problem in Swift code.  Certainly far more responsible than scoped access control.  (This point was made in response to John's reference to 10,000 line files).
> 
> I have nothing at all against all the uses of files you describe above.  I just don't think files are the answer to every use case for access control.  There are strong pragmatic reasons for allowing encapsulation of types within a file.  
> 
>> 
>> What *can't* file-based organization do, but explicit or implicit scope boundaries can? Well, they can't nest. But as I've previously argued, limiting access to the confines of a nested scope is often problematic because you can only address the current scope, not a surrounding scope;
> 
> This is not an inevitable state of affairs.  Again we have a tradeoff and could allow addressing containing scopes if desired.  But let's not have that debate right now.
> 
> For scopes smaller than a file, the current scope is by far the most useful one to address.  It is the one that we must be able to address to protect access to the state of helper types or small types that closely collaborate.  In cases where this kind of tight encapsulation isn't necessary "oversharing" isn't nearly as big a deal in cases I've run across.  
> 
>> thus, sharing even one level up requires you to "overshare" with the entire file or even (if there's no `fileprivate`) the entire module. Unless you start building `friend`-like features, of course, but I believe John McCall already discussed why that's a bad idea:
> 
> I don't recall anyone advocating for `friend` or anything along those lines.  I agree that would be a bad idea.
> 
>> 
>>> access control is never going to be sufficiently expressive to express constraints like "this method can only be called from this other method" — we actively would not want to encourage programmers to engage in that level of pedantry, because some of them will, and they'll make themselves miserable and accomplish nothing of value, and they'll blame Swift (correctly) for implying that it was important.
>> 
>> 
>> We could, of course, introduce some kind of explicit declaration for private scopes. But why? Files usually match or at least approximate the desired boundaries
> 
> This is true much of the time, but there are a very meaningful number of cases where it is not true.
> 
>> , and when they don't already, they usually can be made to do so.
> 
> This is simply not true today.  When I have a small helper type T that should only be visible to type U they must be in the same file.  This necessarily means that without scoped access T cannot hide any details from U.  This is not at all uncommon in projects I have work on.
> 
> It might be true with submodules in the future.  But we don't yet know what the future holds and we're debating a proposed change to the language that will happen long before that future.
> 
>> Sometimes they can't, but that merely means you have to be careful. How much syntax should we really dedicate to a corner case, especially one that doesn't prevent anything from being written, but merely requires you to exercise caution?
> 
> I don't consider this to be a corner case.  It impacts a very meaningful percentage of the code I write.  I really don't think one access modifier is too much to ask here.
> 
> One of the reasons I like Swift so much is that in most cases when the language can help catch mistakes at compile time it does that.  The community generally values this greatly.
> 
> I have learned over the years that no matter how cautious I am I still make mistakes.  I would prefer to have the compiler catch this kind of mistake immediately rather than relying on a human to eventually notice the mistake.
> 
> Moving the burden of catching these mistakes from the compiler back to humans would be a change that is actively harmful IMO.
> 
>> 
>> -- 
>> Brent Royal-Gordon
>> Architechies
>> 
>> 
>> 
>> P.S. Unless I misunderstand your meaning, I believe that this statement in your later post:
>> 
>>> The implicit outer scope at the file boundary is one that only exists because it was introduced in Swift 2.
>> 
>> Is anti-historical. The file scope is as old as `private` (and access control) in Swift, which was part of Swift 1 beta 4: <https://developer.apple.com/swift/blog/?id=5>
> 
> Well that was an embarrassing mistake!  I remembered it not being in the original release but apparently had forgotten that it was added during the Swift 1 beta cycles.  I should have checked before posting.  Thanks for the correction.  
> 
>> 
>> 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170326/59ec1359/attachment.html>


More information about the swift-evolution mailing list