[swift-evolution] A Comprehensive Rethink of Access Levels in Swift
matthew at anandabits.com
Sat Feb 25 17:27:35 CST 2017
> On Feb 25, 2017, at 4:23 PM, Jonathan Hull <jhull at gbis.com> wrote:
>> On Feb 25, 2017, at 1:43 PM, Matthew Johnson via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> On Feb 25, 2017, at 3:27 PM, Kevin Nattinger via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>> Additionally, the design allows ‘final’ to take any one of those visibility levels as a parameter, to indicate that the type should be treated as ‘final’ at and above the specified scope. Thus ‘final(public)’ prevents subclassing outside the module, while ‘final(internal)’ prevents it outside the ‘private’ scope. For consistency, ‘final(private)’ is also permitted, although it means the same thing as ‘final’ by itself.
>>> I feel final(public) could be confusing given how we currently have private(get/set). What about public(final)? That’s at least consistent with current access syntax.
>> The syntax that most closely aligns with private(set) is internal(inherit). The parameter expresses a capability not a limitation and it is used with an access modifier that specifies the *maximum* access level that is allowed to have this capability. `final` is also misleading in this context because it implies to most people that there are *no* subclasses (inside or outside the module).
>> The only way to express `final` in Swift’s access control system would to have a `never` access modifier allowing you to say `never(inherit)`. I can’ think of any other uses for an access level like this. `final` is much more direct and is the term of art. I don’t see a reason to change the spelling of `final` and parameterizing it doesn’t make any sense - if something is `final` it is `final` in *all* scopes.
> hmm… ‘semifinal' for things that are closed outside the submodule?
The name that has usually been used for this is `closed`. But no word that expresses a *restriction* is going to fit well into Swift’s access control system. The approach of Swift’s access control system (which I believe to be the best approach) is to express an upper bound on a *capability*. The basic capability is visibility of the symbol. That model is extended by talking about specific things a user might *do* with that symbol and setting an upper bound on that specific use: `internal private(set)`. `internal` is the bound on visibility and `private` is the bound on the ability to `set` the property.
`closed` is most naturally expressed in this system as `public internal(inherit)`. One interesting thing to observe is that `internal` is the default level of visibility. If we extend that default to capabilities as well as visibility the result is to give us exactly the behavior we want from a naked `public` annotation. We want to require an annotation for exposing anything (a symbol *or* a capability) outside the module. This means that `open` is really an alias for `public(inherit)` if we follow the principles underlying the model we have for access control.
Following this line of reasoning to its natural conclusion we can observe that the current interaction of `var` properties with access control *do not* follow the principles we have set forth for Swift. `public` variables *automatically* have `public` getters violating the principle that `internal` is the default. We could fix this by requiring users to say `public(set)`. I imagine there would be a lot of screaming in the short term if we proposed this. But it would put Swift’s access control system on a more consistent and principled footing.
I think there are three major reasons people have trouble with the current access control system. The `private` / `fileprivate` name situation is an obvious one. But I think perhaps more important is the fact that the system has idiosyncrasies such as introducing `open` rather than following the capability model of `set` (which would have resulted in `public(inherit)`). Another idiosyncrasy noted above is that `set` defaults to the level of the getter rather than `internal` (but bounded by visibility). Finally, the principle that underlies the access modifiers (modulo the idiosyncrasy of `open`) is that of a bounded scope. But this principle is implicit - each keyword is defined on an ad-hoc basis rather than making the underlying principle explicit and defining any shorthand we want in terms of that (as syntactic sugar for common cases and recommended defaults).
This is why I strongly believe the best approach is to make the underlying principle of scopes and capabilities with an `internal` default explicit. I think this principle is easy to teach and easy to understand once it is made explicit. It is a wonderful (but currently rather hidden) aspect of Swift. If we can teach users to understand this principle and to learn the names of scopes and capabilities they will not find it difficult and complex. They will find it as easy to learn as a library function: we would have `scope(<capbability name>, <scope name>)`**. The capability would be defaulted to basic visibility. The scope would be defaulted to the current scope.
Consider scope(inherit, public)`. It would read like “the scope of inheritance is public”. It tells the reader *exactly* what is happening. We could conceptualize shortened like `open` as something like `accessalias open = scope(inherit, public)`. Tools could even allow you to cmd-Click to the definition to see a definition like this. Users would learn something that is no more complicated than a single function call and any time they are confused by shorthand they could cmd-Click or look up documentation that explains it clearly in terms of this "function call”. Note: `accessalias` wouldn’t be a real thing, just a way to document and explain the shorthand keywords we have for “soft defaults” and common conveniences.
This seems to me like a very simple and elegant yet extraordinarily powerful system. I think it’s much better than a small collection of ad-hoc definitions of very specific behaviors.
**Anyone who has been reading my thoughts on this might notice that I am using `scope` rather than `scoped` in this post. As I was writing this I realized that when we think of access in terms of capability it reads better with `scope` than it does with `scoped` (i.e. “inheritance is scoped public” does not read as well as “the scope of inheritance is public".
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the swift-evolution