[swift-evolution] Type-based ‘private’ access within a file
david at alkaline-solutions.com
Wed Apr 5 17:32:32 CDT 2017
> On Apr 5, 2017, at 3:27 PM, Tony Arnold via swift-evolution <swift-evolution at swift.org> wrote:
>> On 6 Apr 2017, at 02:54, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution at swift.org> wrote:
>> The developer adds a free function to the file. Or an extension of another type. Or another type entirely. And they put it in the same file because it needs to work with the implementation details of the existing type.
> This is the part of all of these conversations that concerns me deeply. Is the assumption that it is now “best practice” to place all shared implementation into a single file? Aren’t those files going to become quite large in real world use? That’s a terrible thing to encourage!
It depends on what these access control levels mean to the developers, not just on how they affect the compiler or symbol exposure.
For instance, my viewpoint is that private is for hiding implementation details of a type that someone editing elsewhere within the same module shouldn’t need to understand to do their job correctly. That means all unsafe details, and safe implementation details to be hidden to decrease coupling.
Fileprivate is used when unsafe details are needed to share implementation details outside a type with other types or free functions, by embedding all the accesses into a single file. Today, it also used to expose safe and unsafe details to extensions of the type within the same file.
Don't fall into the trap of just always using the lowest acceptable level of access control, rather than choosing levels below “internal” to reduce coupling of implementation details and preventing unsafe access to a type. If you focus on using the lowest acceptable access control level, you’ll wind up structuring your code around reducing said access controls - which will indeed result in all the code getting thrown into a single file ;-)
> My experience working with protocol conformances in Objective-C over the years is that breaking protocol conformances into categories/extensions on the original type *in separate files* is the best way to keep things small, testable and focused.
> Separating protocol conformances into separate files, whilst still being able to access private members of the extended type promotes smaller files, and more focused implementation within those files.
Based on the above, I’d say my viewpoint is:
- If your extension is based on details that don’t require knowledge of the internal workings of the type, then you should be able to expose those details of the type to the module, and author the protocol conformance in any file you like. All third party conformances of your type to a protocol are necessarily of this type, because they don’t have internal knowledge or access.
- If your extension requires internal knowledge of the type, it should be consolidated into a single file with the type so that changes to the internals are localized (and also promotes higher cohesion of your code). Implementing Encodable/Decodable are likeliy examples of this.
- there is a grey area where there are details which are safe but that you don’t really want to expose due to implementation details, or could be potentially be written better with implementation detail access. Then you have a trade-off to decide one way or another. Implementing CustomDebugStringConvertible or Equatable might be examples of this.
> I know Swift has different purposes for different people here, but ultimately it’s supposed to support great application development for Apple’s platforms, right? To my eyes, `fileprivate` should never have been introduced without some kind of `typeprivate` (similar to how things would be declared back in Objective-C land with a `*+Private.h` header that you could import when it was needed). Right now, there’s no way to duplicate that pattern in Swift - I would need to make anything I wish to access `internal` which exposes it to everything within the application/module - surprisingly, this is a regression from the tooling and access levels I had available in Objective-C.
I personally fought against the inclusion of scoped private originally because I feel that it was sufficient that developers have to understand the code that they are working on - they need to understand the public details of dependent modules, the internal and public details of the module they are working on, and all the details of the file they are working in. I didn’t see the need to go smaller than file, to say they have to understand the file private details of the file they are in and the private details of the type they are editing. But c'est la vie.
The use of a type scoped private independent of a same file restriction would be to spread implementation details on a type across multiple files, which could be argued to be a bad practice. If nothing else, it means a developer can work around your attempt to encapsulate and hide implementation details by creating accessor methods in an extension elsewhere in the codebase, and that this would not be revealed until something broke.
More information about the swift-evolution