Unless I'm missing something Brent, your suggestions still wouldn't allow the 
developer to provide a public class in a module designed to be subclassed by 
clients in another module and access these "private" details, which is a real 
problem I'm having in my current project.

I have a framework which provides an abstract model object designed to be 
subclassed (and yes it has to be a class and not a struct for a multitude of 
reasons ๐Ÿ˜›) by clients of the framework. There are several convenience methods + 
properties I have exposed for subclasses to use, but they should really be 
implementation details; a client using these model objects should not have to 
know about them. Even worse, several of the properties are mutable so the 
subclasses can modify them, but they certainly should *not* be modified by 
anything else.

Right now, I'm limited to simply commenting something akin to "DO NOT CALL" 
next to these methods/properties, which definitely goes against Swift's safety 
focus. For these reasons, I'm 100% in support of a protected access control 
modifier.

> On May 28, 2016, at 8:11 PM, Brent Royal-Gordon via swift-evolution 
> <swift-evolution@swift.org> wrote:
> 
> To begin with, I'm not a fan of `protected` access. But even leaving that 
> aside, I have a few questions and critiques.
> 
>> A common case is the UIView from UIKit. Many developers are tempted to make 
>> this call:
>> 
>> view.layoutSubviews()
>> The documentation says: "You should not call this method directly. If you 
>> want to force a layout update, call the setNeedsLayoutmethod instead to do 
>> so prior to the next drawing update. If you want to update the layout of 
>> your views immediately, call the layoutIfNeeded method."
> 
> This example is illuminating in several ways.
> 
> * The rule is not simply that "only the class should call 
> `layoutSubviews()`"; it is effectively "*you* should never call 
> `layoutSubviews()` except when `super`ing up from your override". Calling 
> `layoutSubviews()` from `insertRows(at:)` is just as much a mistake if 
> `insertRows(at:)` is part of the class as if it is not. So isn't `protected` 
> insufficiently strict to properly serve this use case?
> 
> * At the same time, something outside `layoutSubviews()` has to be able to 
> call `layoutSubviews()`. In the case of UIKit, though, that "something" is 
> always within UIKit itself, never outside it. So should `protected` have a 
> "bottom", a level below which calls are unrestricted? For instance, in 
> UIKit's case you might have `protected fileprivate`, meaning "anything up to 
> `fileprivate` has unrestricted use; anything above that can override and 
> `super` up from its override, but not use it any other way".
> 
>    protected fileprivate func layoutSubviews()
> 
> * `layoutSubviews()` is also something you should probably always `super` up 
> to. Have you considered addressing `super` requirements at all?
> 
> In short, is a traditional `protected` really the feature you want to handle 
> this use case, or would a very different design actually suit it a lot better?
> 
>> When declarated by a class the protected member will be visible to the class 
>> itself and all the derived classes.
> 
> In what scope? The same as the class?
> 
> Is there not room for, for instance, "usable without restriction in this 
> file, override-only in the rest of this module, invisible outside it"? For 
> instance, `internal(protected) fileprivate`, or perhaps `internal(override) 
> fileprivate`? `layoutSubviews()` might then be `public(override) 
> fileprivate`โ€”the ability to override is public, the ability to use it 
> unrestricted is filewide.
> 
>    public(override) fileprivate func layoutSubviews()
>    internal(override) fileprivate func privateSubclassingHook()
> 
>> public protected(set) var x = 20
> 
> Of course, that might be difficult to combine with the `(set)` syntax. 
> `public(set: override)`, maybe? With, for instance, `public internal(set: 
> override) private(set)` if you want the property's getter public and its 
> setter overridable internally and callable in private scope.
> 
>    public(override) fileprivate func layoutSubviews()
>    internal(override) fileprivate func privateSubclassingHook()
>    public(get, set: override) internal(set) var x = 20
> 
> But there's something about this that's starting to seem a little rotten. I 
> think the problem is that we're not really trying to widen the ability to 
> override, we're trying to restrict the ability to call. Let's try 
> restructuring along those lines:
> 
>    public fileprivate(call) func layoutSubviews()
>    internal fileprivate(call) func privateSubclassingHook()
>    public internal(set: call) var x = 20
> 
> That seems much cleaner to me.
> 
>> If the member is declared as final then it will be visible but not can be 
>> overrided by the derived classes. Just like it works with other access 
>> levels.
> 
> With the "overridable but otherwise unusable" conception I'm suggesting, this 
> would not be the case, of course.
> 
>> Protocols
>> 
>> Protocols do not declare access level for their members. So the protected 
>> access level is not applicable here.
> 
> But `protected` is quite different from other access levels; it does not 
> limit the visibility of the symbols, but rather their use. And protocols face 
> the same sort of problem as classes, where certain members are essentially 
> override hooks and shouldn't be called directly outside a particular scope.
> 
> So I think we ought to allow `accesslevel(call)`, but not a plain 
> `accesslevel`:
> 
>    public fileprivate(call) func layoutSubviews()
>    internal fileprivate(call) func privateSubclassingHook()
>    public internal(set: call) var x = 20
>    internal(call) func protocolConformanceHook()
>    fileprivate(set: call) var onlyProtocolSetsThis: Int { get set }
> 
>> Extensions
>> 
>> Extensions will not be able do be protected nor their members.
> 
> This is very vague. There are several things extensions might try to do with 
> protected members:
> 
> * Declare new ones
> * Override existing ones
> * Call existing ones
> 
> Which of these, if any, are permitted? Why?
> 
> In my conception, I would permit extensions to behave as the type they 
> extended did. Extensions could declare new members with restricted calling 
> and override existing ones. They would not be able to call, except when 
> supering from an override, unless they were within scope of the `call` access 
> control. In other words, they'd behave just like any other code at that 
> location. That's how we want extensions to work.
> 
>> But nested declarations will be allowed, so this code will compile:
>> 
>> // We can declare a protected class (or struct, enum, etc.) if
>> // and only if they are nested inside other type.
>> public class MyPublicClass {
>>   protected 
>> class MyProtectedClass {
> 
> What does it mean to "use" a protected class, though? Clearly you can call 
> its methods, if only through AnyObject or a non-protected superclass or a 
> protocol it conforms to. Does it mean you can't instantiate it? Does it mean 
> you can't subclass it? Does it mean you can't call methods that aren't on its 
> supertypes? All of the above? None?
> 
> One more thing that didn't come up: Testability. I believe that importing a 
> module with `@testable` should disable its call restrictions, even ones 
> inherited from outside that module. Thus, even if *you* cannot call your 
> `layoutSubviews()`, your test suite can.
> 
> So, in short, my counter-proposal is:
> 
>    public fileprivate(call) func layoutSubviews()
>    internal fileprivate(call) func privateSubclassingHook()
>    public internal(set: call) var x = 20
>    internal(call) func protocolConformanceHook()
>    fileprivate(set: call) var onlyProtocolSetsThis: Int { get set }
> 
> In other words:
> 
> * There is a new aspect of the member, `call`, which controls the ability to 
> actually call the member, as opposed to overriding it. No `call`, no calling 
> (except when `super`ing up from an override).
> 
> * `call` is used in combination with one of the existing access modifiers: 
> `public(call)` `internal(call)` `fileprivate(call)` `private(call)`. `call`'s 
> visibility is always less or equal to the member itself.
> 
> * To control the callability of a setter independently from both the getter 
> and the overridability of the setter, use `set: call`.
> 
> * Extensions behave just like type definitions at the same location with 
> regards to `call`.
> 
> * Protocols can use access modifiers with `call` to prevent unauthorized code 
> from calling a member. The access control level to implement a member 
> continues to be as wide as the access control level of the protocol itself.
> 
> * `@testable` disables `call` restrictions on the types it imports, so the 
> test suite can call any visible member, even ones inherited from other 
> modules.
> 
> * There should probably also be some sort of "super required" warning/error, 
> but this is an orthogonal feature and can be left for a separate proposal.
> 
> I think that feature will be closer to the one you actually *want*, as 
> opposed to the one that other languages have cargo-culted from SIMULA-67.
> 
> -- 
> Brent Royal-Gordon
> Architechies
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to