I wanted to propose a second simplification to the access system which would
have several benefits:
• It would remove ‘fileprivate’
• It would allow extensions to be in their own files
• It would serve the needs of ‘protected’ without the complications
involved in that
• It would serve some of the needs of submodules (but also work
together with them really nicely when we have both)
• It would allow protocols to have something similar to private
methods, which are not exposed to callers of the protocol, but still required
for conformance
My proposal is to add a ‘hidden’ access modifier which would act as a separate
AXIS as opposed to a new level. Thus, it could be combined with any of the
other modifiers (e.g. 'public hidden var’). The ‘fileprivate’ level would be
removed.
Anything which is marked hidden is not visible outside of the file it is
defined in. That visibility can be explicitly restored on a per file basis
(but only within the defined access level). Take a look at the following:
//File: MyStruct.swift
struct MyStruct {
var a:Int
hidden var b:Int
}
extension MyStruct {
var biggest:Int {return max(a,b)} //We can still see ‘b’
because it is only hidden outside the file
}
Here we see that ‘b’ behaves very similarly to the way fileprivate works now.
‘b’ is technically still internal, but it is hidden and thus can’t be seen
outside the file. The difference is that instead of being forced to add all of
our extensions in the same file, we can organize them however we prefer.
//In another file
import hidden MyStruct //This exposes all ‘hidden’ items from the file
MyStruct to this file
extension MyStruct {
var c:Int {return a + b} //We can see ‘b’ because of the
‘import hidden’ statement
}
Notice how the intent has been shown here. ‘b’ is marked ‘internal’, which
means we know that no one can see ‘b’ outside of the module. In addition ‘b’
is marked ‘hidden’, which means that the author is saying that this should only
really be accessed from extensions, subclasses, or types which would be called
friends in other languages. The actual guarantee is still ‘internal’, but a
caller does have to explicitly request the access by typing ‘import hidden’,
which will stop accidental/casual misuse. (If/when we get submodules, then we
can make tighter guarantees)
This also allows a class to “hide the ejection seat levers” from its callers,
while still allowing access for subclasses and extensions (i.e. it does most of
what ‘protected’ would do, but with swift’s simpler file-based access model)
The same is true of protocols. There are often methods I have to include on
protocols which are needed by the default implementations, but NEVER meant to
be called directly by callers of the protocol. If vars/methods of a protocol
are marked hidden, then they would be hidden from callers of the protocol
outside the file. They are still required for conformance, however*.
protocol MyProtocol {
var a:Int
hidden var b:Int //Conformers still need to provide this, but
callers can’t see it
}
extension MyProtocol {
func doSomethingBasedOnB() {
//The extension can see b in the same file, but callers
in other files won’t have access to ‘b’ directly
}
}
I think all of this works really well with Swift’s goal of progressive
disclosure. Users of protocols/classes/etc… are not exposed to the internals
necessary for extension (e.g. it won’t come up in autocomplete) until they
actually need to conform/subclass/extend. Users won’t even need to learn about
‘hidden' until they are trying to extend a type which uses it (in a way which
requires access to those internals), or they are writing their own framework.
It has a simple and consistent meaning based on Swift’s original file-based
access, but allows power/flexibility (without too much bookkeeping/boilerplate)
where needed.
Thanks,
Jon
* One detail needed to make things useful for protocols, is that both hidden
and non-hidden vars/methods on the conforming type should count towards
conformance of hidden vars/methods on the protocol. Basically, marking
something as ‘hidden’ on a protocol means it isn’t seen by the caller (only
conformers/extensions). It makes sense to allow the conformer not to expose
that either, unless desired. It would technically work fine without this
allowance, but it ‘feels right’ and people would ask for it quickly...
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution