[swift-evolution] A Comprehensive Rethink of Access Levels in Swift
matthew at anandabits.com
Sun Feb 26 12:48:50 CST 2017
Sent from my iPad
> On Feb 26, 2017, at 10:58 AM, Charles Srstka <cocoadev at charlessoft.com> wrote:
>> On Feb 25, 2017, at 5:27 PM, Matthew Johnson via swift-evolution <swift-evolution at swift.org> wrote:
>> 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".
> The nice thing about that is that we could declare something as private public(inherit), making it not callable but possible to override, which would be a great fit for methods that are only intended as override points and are not meant to be called directly, like the many such methods on NSView.
That's interesting. I hadn't thought about this. It would complicate the model a little bit - I have been thinking in terms of capabilities as additive to the basic capability (i.e. when a setter is available so is the getter, when you can override you can also call, etc).
That said, there have been requests for override-but-not-call and you're right that this can be an important guarantee for a library. Would you expect override to imply the ability to call super? Or would you expect this contract to be used in cases where the subclass is not allowed to call super at all? That latter obviously fits the semantics of this access control model better and I imagine it is often what you would want (for example if the override points are part of a template method pattern).
There have also been requests for set-only properties although I'm not as familiar with the use cases for that.
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the swift-evolution