By the end of the day, if the main issue is really just the implementation complexity, then it would really awesome if we can find some Swift compiler pioneer who would volunteer to hack a quick prototype of such an access control so that we can play around with it for a bit. It does not have to be perfect and bug free, but it would immensely help in thread.
So in this response I'd like to call out to you the Swift compiler pioneer, if you have a little spare time to hack a quick prototype snapshot, we would really appreciate your support, regardless the potential failure of this discussion or it's possible success.
I think your position is quite clear at this point. Mine follows:
As a general purpose language, Swift should be as style-agnostic as possible. I think that highly-opinionated languages only get used by those who share (most of) the opinions of the language developers.
The purpose of this thread is to gauge interest in change. Once you have registered your opinion, constant reiteration is just noise.
I do accept that languages can be opinionated and programmers feel like priests sometimes... but if enough new evidence mounts up then yes, it should get challenged and re-discussed.
Opinionated does not mean "logic and explanations are not necessary these are rules set in stone never to be argued about again, deal with it or get lost". If it becomes an excuse for it then we would have a problem and I do not think we are there ;).
I share your opinion here. Furthermore I think we can do better than that if we accept that we did mistakes in the past and learned from them and are not afraid to try and fix them by optimizing the overall experience for everyone who uses Swift.
Above I noted that the presented possible solution would allow us to settle the discussion for in-line unit tests very quickly since with a type bound rather then type + file bound private default we would be able to test most of our code in the same file and from other files without the need to create additional unit test swift files that mirror a specific swift files to gain required visibility. The true file private access can even be deprecated after a few more language iterations since it's probably will be super rare, but in the presented migration path would be required for source compatibility.
Two different models (file based and type based) for one thing (access control) adds complexity for users -- and this topic is already quite complex in Swift.
I wouldn't even say that the file based approach is superior, but it's what has been chosen.
Also, access is already mingled with the ability to override, and this complicates things even more...
I'm the last to oppose a fundamental change, because I believe that even the original model of private/internal/public was better than what we have now -- but during the last amendment, I already got the impression that Core just wanted to close the topic, and never touch it again ;-).
Afair partial classes (basically, extensions with stored properties) are still on the table and might be added in the far future -- but I don't think protected will ever be accepted.
Submodules would add an independently useful mechanism (e.g. completes the semi-implemented-at-best feature of import module.submodule) that also has the possibility of removing an access control keyword, or at least not adding them, so I think that's a much more fruitful avenue for exploration. I'm a signatory to the UN Treaty on the Non-Proliferation of Access Control Keywords so I can't endorse this pitch.
I don't quite see how submodules will solve this issue, but this might be because I don't know exactly what the concept of submodules is.
I assume here, that using internal inside a submodule will restrict the access to the submodule and not allow access from the surrounding module. If this won't be the case, and internal will allow access from the surrounding module, then I don't see the value of this feature for this issue.
Under this assumption, I can not use submodules to organise internal types in different files, because I need to declare types public to access them from the surrounding module.
I can not see a reason to restrict this access organisation to public types.
Additionally If I have many large types I want to organise these in multiple files, I need to introduce a submodule for each type and I have no opportunity to allow some functionality of a type to be accessed only from my other submodules, but not publicly and some functionality to be only accessible inside my submodule.
Well this is exactly what I've been saying above and this is also exactly how others argue on how sub-modules can solve that particular issue. You just put your type with all it's extensions from different files into a sub-module and level up every access modifier by one so you won't be using private ever again, but rather internal, public and open. And as I mentioned before, this isn't solving the general issue here nor should a potential sub-module system judge over the failure or success of a different problem from this thread.
Sorry, seems like I missed this. I just remembered that you mentioned the additional overhead of specifying the modules (BTW, having folders as submodules would really complicate code organisation, because you would also have to put everything inside one directory, just to mention this; I bet this was one argument against folder based submodules).
Certainly submodule concepts should not be judged whether they solve this issue or not.
However, a new type of access modifier in combination with submodules could solve our issue:
accessibleFromModule(ModuleName)
This modifier would basically work how @testable import works, but:
You should be able to mark whole modules to make all internals accessible from the other module (like @testable import)
and you should be able to mark only some types and type members to be accessible from the other module.
I have been shown by @hartbit how the current access control is designed to be easily lifted up without source breakage. I recreated the image that I've seen and also made a similar one for the presented solution from the original post.
On the left hand side you see our current access modifiers (except open), where private and fileprivate are bound to the current file, while on the right hand side you see the presented possible solution where the current private and fileprivate are renamed to (file)private and (file)internal (and still are bound to the file) with the addition of a new only type bound private as the new soft default access modifier.
type based visibility control is related but not completely the same mechanism as access control. Joe Groff explained this well here:
In my ideal world,
fileprivate doesnât exist and private means what fileprivate currently does
we get submodules along with a modifier associated with them.
Classes in separate files that share internal details would share a submodule. It doesnât completely solve the issue of âhiding it as completely as possible from myself as I canâ but I prefer not introducing the axis of inheritance as a concern
I'm not an expert here, in either submodules or the details of the Swift compiler, and I don't know exactly what the design should be either, but I'll write some rough notes on my thoughts here that might answer some of these questions or at least open up some discussion.
Public inside the submodule doesn't necessarily mean exported from the module. This is an issue that Swift already has now at the module level, i.e. if you import a module should it then be exported from your module? You might want or need it to be (e.g. you return types from the imported module), you might want to hide the imported module (e.g. the use of the imported module is an implementation detail that isn't exposed in your public API) or you might want to do something conditional (e.g. enable certain functionality if the user importing your module has also imported another module, otherwise hide it). The same solutions could apply to submodules.
Perhaps submodules would have access control keywords attached to them. A public submodule at the top level would then be exported from the module, and available for import module.submodule. An internal submodule at the top level would be usable within the module but not exported.
Submodules could be nested, allowing all sorts of exotic combinations of access control without having to invent new keywords. A nested submodule would then only be exported from your module if all the parent submodules were public up to top level. internal submodules would only be visible within their enclosing submodule.
This all supports progressive disclosure, so that you can write code as usual at top level and never know or care about submodules. Meanwhile people working on larger codebases, or with specific style preferences around large files, can build complex access control systems with nested submodules.
This would hopefully reduce compilation time, because it limits visibility within your module, and allow people to stop splitting things up into modules purely for efficiency benefits.
As mentioned above, it also completes the partially-implemented feature of import module.submodule, only really usable with things like UIKit currently, that has a lot of precedent in other languages and has tooling benefits like not polluting autocomplete with the entire API of your module.
I've been following this discussion for a while, and wanted to share some thoughts on an approach that may not have been brought up yet.
It seems that the main use case for a "protected" access level modifier is for people who like to break large classes into multiple files using extensions. I fall into this camp, as I think many others do. It's a useful language feature that helps keep code manageable while dealing with the realities of Cocoa development. I think we could improve the ergonomics of this pattern without having to change how access modifiers work.
What if there were a way to mark a file as being a "continuation" of another file -- i.e. they'd be separate in the file system, but treated by the compiler as if they were concatenated into a single file? For example, if I had a class Foo defined in Foo.swift, then in Foo+More.swift:
// Foo+More.swift
#continuation("Foo.swift") // strawman syntax
extension Foo {
// Access to Foo's private members, because
// this code is treated as part of Foo.swift
}
As an alternative, perhaps the File.swift / File+Something.swift naming convention could be elevated into an actual compiler-recognized pattern for denoting these continuations.
The way I see it, this would remove most of the pressure for a protected access level. The file-based access control model would be upheld (in essence) but with more flexibility to accommodate different styles of code organization. And it could probably be done additively.
There is still a huge misunderstanding and spread of misinformation in this thread. I was never ever pitching the protocted access modifier here, not even close. ;-) protected was rejected, for good reasons, I do not want to bring this discussion back in here at all, that was and is not my intention.
If it isn't clear enough I can edit the title to explicitly mention that we're not talking about a new way for protected in this thread, at least I'm not doing that.
Here a simplified example for those who may have misunderstood my intention:
///=============--------------- FILE_A.swift ---------------=============//
class A {
private func foo() {
print(42)
}
}
class B : A {
func bar() {
// error: 'foo' is inaccessible due to 'private' protection level
foo()
}
}
///=============--------------- FILE_B.swift ---------------=============//
extension A {
func foo2() {
foo() // should compile now!
}
}
extension B {
func bar2() {
// error: 'foo' is inaccessible due to 'private' protection level
foo()
}
}
///=============--------------- FILE_C.swift ---------------=============//
protocol Baz {
func doWork()
}
extension Baz {
private func prepareThings() { /* ... */ }
}
///=============--------------- FILE_D.swift ---------------=============//
extension Baz {
func doWork() {
prepareThings() // should compile now!
}
}
To this day, this isn't possible because the current private as mentioned dozends of times is bound to the type & to the declaration file. We're not introducing protocted here or any possible new hacks to create protocted. We would only rename the current fileprivate and private which is an easy migration since the compiler only has to rename two keywords and then we're lifting up a new private to not to be bound to the current file as for it's visibility. The newer private won't allow you to inherit anything, it also won't allow you to override, it just will solve one issue that makes the current access control from the usability perspective very inflexible and forces people to create massive files with 5000 lines of code instead organizing them. (This also depends on the developers habit, but Swift should not prefer one over the other, otherwise we're doing something really wrong here.) As been said by others Swift should be style-agnostic, so should the default for private be style-agnostic.
Again, even though a lot of you may think every discussion about access control is radioactive, if you're a passion Swift developer as I am then this discussion won't hurt you, as I'm trying to keep it as cool as possible. I really do not want this discussion to escalate in flames, but rather discuss the pros and cons in a nice constructive manner. Thank you everyone for your understanding.
I think you touched the core issue here. It is basically the divide between opinionated and non-opinionated (what I call consensus based) strategies.
An opinionated language can take a stance here and say e.g. that it favors convention over configuration. It will use the file system for access control, simplify the model and whoever wants to do things differently has to adapt. A good example of this kind of approach is Go. Pros: You keep the language bloat-free and simple, you tend to have features that integrate well with each other. Cons: You make a lot of people angry, possibly lose a lot of users. Go still has at least one blog post per week complaining about generics, 9 years after v1.0.
A consensus based language tries to accomodate everyone without favoring any specific approach. A good example here is C++. Pros: You build a more flexible language, you support different styles of programming, you don't alienate your users. Cons: It can lead to bloat, features that are all over the place and basically end up building a new C++.
Even though I'm personally in the opinionated group, I have to admit that since Swift Evolution started the core team's direction definitely favors the consensus based approach.