[swift-evolution] access control

Taras Zakharko taras.zakharko at uzh.ch
Mon Jan 25 17:40:55 CST 2016


I feel the discussion is starting to get a bit circular :) At this point, think it is important to try to discover additional benefits of both solutions rather then reiterating the same arguments, so I think that Matthew is going the right direction by trying to bring practical aspects of the issue into the view. So far, the presented arguments were mostly ideological. As far as I understand it, Ilya’s point is based around a very rigorous notion of encapsulation (which I personally disagree with, but it doesn’t matter) and the necessity to enforce the design invariants. The definition scope access here aids as a safeguard that separates the public and the private interface. This is obviously an important feature and the reasoning is principally valid. On the other side, the opponents argue that such separation is barely useful on practice, because a single file in a well designed code is usually restricted to a few closely related declarations, so the chance to mix up the private and the public interface is actually pretty slim.  And the private interface is invisible outside the file level, so the large scale aspect of this issue never becomes valid. There is undoubtedly some truth in this as well. Personally, I would be ok with either, but I am more tending towards the view that definition scope access is unnecessary. Let me present some arguments:

1. It only solves a small subset of a larger problem. If we really give were to stress the importance of enforced design invariants that much, then definition scope access is actually not enough. What we really need is a permission system, where we describe the exact usage of a member, e.g. this method can be only called from that one or this method can’t be called if the state is like this etc. In the end, unless we have built-in means to prove our code, invariants need to be tested with tests and assertions — and these should catch any misuse of private APIs.

2. The problem it solves is arguably barely worth mentioning. How often do you have to add principally new types to a file with other declarations? How often is it easy to confuse private and public interfaces? It is really such a common thing to accidentally misuse an API and do not notice it? Usually, I would not bring any substantial functionality into a source file written by others without first gaining basic understanding about the other contents of that file. And also, usually, if I am about to bring any substantial functionality into a source file written by others, I do it because I understand what is going on there and I just want to improve/enhance something. For instance (to bring some anecdotic evidence into play), I am mainly working with a language that lacks any access declarations whatsoever (R), and I never really had an issue with this. And I am often employing techniques which involve metaprogramming and on-the-fly replacement of definition scopes, so its fairly complex stuff.  It is usually very clear which functionality is meant to be private and which is meant to be public, and to be honest, I quite frequently find myself accusing the private one for a number of reasons (usually efficiency and performance). 

3. It assumes that the designer gets the design correct on the first try. How do you know whether you should declare a property local or private? Maybe you start as local (default) but at some point realise that it would be useful to open it up to other scopes for efficiency/other reasons? Especially in the initial design phase, there is a lot of moving around. Redeclaring invariants is not without effort, even though this is a very minor criticism point. Just wanted to mention it for the sake of completeness (of what came into my mind).

4. It hinders competent extenders. Imagine you understand the code really well. You want to add a new auxiliary public function, and it would be much more efficient if you accessed the private interface directly. However, the original designer did not anticipate your use case, so they declared that interface local. So you’d need to go and change the modifiers to private, which is kind of unnecessary effort. 

To sum it up, I believe that restricting access like that gives you a sense of false security. It is not really likely to prevent bugs from happening, but it does limit the extendability of the code. The issues it aims to solved can be easily solved by minimal degree of coding discipline (such as: do not go changing around stuff in a file unless you understand what you are doing) and I doubt that experienced programmers are likely to encounter these issues very frequently (I know I never did, and I wouldn’t even call myself that experienced). Code testing should be performed by debug assertions and unit tests, which will easily detect any relevant case of API misuse. In the end, I believe that definition scope restricts mostly for ideological, but not practical reasons, given the coding style most appropriate with Swift. 

Can we add a definition scope access to Swift? Sure, it won’t change much. Will we benefit from this change? I doubt so. The current system is good enough and its quite elegant in allowing one to implement tightly related functionality. 

Finally some comments on Matthew’s post

> In some cases, this is because there is a nested type involved and the `private` members are inside the nested type.  They should not be visible outside the scope of the nested type.  


But sometimes, if one wants to extend functionality or improve efficiency, and know what they are doing, having access to these private members could be beneficial. If they should not be visible outside of the scope in the first place, the person who implements the container type probably knows it and is unlikely to access them directly

> One other case that didn’t appear in Alamofire, but I have seen elsewhere is a case where you conform several types to the same protocol (usually a simple protocol) in the same file.  In this case there may be helper methods that support the protocol implementation but should not be visible to the other implementations of the protocol that are in the same file.


How likely is one to call one of those helper methods in actual code? I don’t see any potential for confusion at all. Unless you are working on a type that extents the type with the private method, but it doesn’t seem to be your example

> It seems like “current scope” is probably the most frequent intent of `private`

That is undoubtedly true. However, the question should be put the other way: how frequently does the file scope obscure, obfuscate or otherwise denies this frequent use?

> I suggest those who are pushing back on this look through some real-world code.  How often do you actually need the file-level visibility of a `private` member?  This is likely only needed in a significant minority of uses of `private`. 


Personally, I use it quite often because of the way I like to design things as groups of tightly interdependent components (friends, if you want) who are aware of each other’s inner workings. I also want to have full access to the interface of any project I work on because I trust myself to make the judgement whether I am allowed to use a particular functionality or not. Definition scope restricts me without giving me any benefit I can immediately recognise. 

Best, 

 Taras


> On 25 Jan 2016, at 23:16, Matthew Johnson via swift-evolution <swift-evolution at swift.org> wrote:
> 
> I want to try and offer a concrete example of the usefulness of this idea in real code.  I had a look through the popular Alamofire library.  Every use of `private` in that library could actually use `local` instead if it existed.  
> 
> In some cases these would be equivalent because there is only one type / scope in the file anyway.  However, in most cases they actually do communicate something different.  There are a few ways this is possible:
> 
> In some cases, this is because there is a nested type involved and the `private` members are inside the nested type.  They should not be visible outside the scope of the nested type.  
> 
> In other cases, there are extensions of other types.  These extensions add methods that are closely related to the primary type / extension involved in the file.  The private members of the type 
> 
> One other case that didn’t appear in Alamofire, but I have seen elsewhere is a case where you conform several types to the same protocol (usually a simple protocol) in the same file.  In this case there may be helper methods that support the protocol implementation but should not be visible to the other implementations of the protocol that are in the same file.
> 
> It seems like “current scope” is probably the most frequent intent of `private`, while `file` is actually less commonly necessary, but is certainly the right thing in some cases (and is much better than `friend`).
> 
> I agree with Ilya that it would probably be better if `private` was actually scope-based.  Rather than `internal` we could have `module` and `file` access modifiers to make it clear when there is broader visibility and make it clear exactly what that visibility is.  This would be my preference and I think it would provide the most clarity.
> 
> On the other hand, changing the meaning of `private` is probably not going to happen at this point (or would at least likely receive even more pushback than `local`), thus the proposal to add a new modifier and not change the meaning of existing modifiers.
> 
> I suggest those who are pushing back on this look through some real-world code.  How often do you actually need the file-level visibility of a `private` member?  This is likely only needed in a significant minority of uses of `private`. 
> 
> Saying what you mean and meaning what you say adds clarity to the code.  It helps readers and future maintainers of the code understand the thinking of the author.  Right now, we do not have the ability to say what we actually mean with regards to access control in a significant number of cases.   It is the inability to express the most frequent intent of `private` that Ilya’s proposal is attempting to address (while not taking away the necessary ability to share visibility within a file in some cases).  
> 
> -Matthew
> 
> 
>> On Jan 25, 2016, at 3:44 PM, Thorsten Seitz via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>> 
>>> Am 25.01.2016 um 20:33 schrieb Ilya Belenkiy via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>>:
>>> 
>>>> There would be no difference at all between local and private if you had one class per file.
>>> 
>>> AND if this rule was enforced by the compiler. This would also have to be one extension per file, even if it’s one line of code.
>> 
>> Why should the compiler enforce this? That’s my design decision.
>> For the same reason the compiler does not enforce where I have to put „private“ (it can make suggestions like many IDEs do and offer fix-its, like „this method can be made private as it is not used outside the class“ or „this class can be put into its own file as its private methods are not used by other components in this file“.
>> 
>> 
>>> Since this rule is not enforced, at most, this is coding by convention. By the same reasoning, we could have just one type, object, and name every variable by including the type name we want it to be. No need for a strong type system. And anyone insisting that we need a type system would surely be wrong because there would be a very simple solution — just make the type name part of the variable name.
>> 
>> No, there is a clear difference: making the type name part of the variable name enforces no compiler checks whereas putting something into different files does. 
>> 
>> 
>>> And yet, Swift does have a strong type system. It should have strong access control for the very same reason: the compiler can enforce it and eliminate lots of human errors.
>>> 
>>>> It seems very very bold to me to say that Swift "doesn't support encapsulation" but that local would solve that problem.
>>> 
>>> And yet both statements are true: it is possible to break the class invariant right now without modifying the class source code, and “local” would solve that problem.
>> 
>> But you need to modify the file. With „local“ it is possible to break the class invariant without modifying the source code of a private method just by adding some public methods that call the private ones. Seems quite the same to me.
>> 
>> That being said I have nothing against a „local“ access modifier but I personally don’t see a real need for it. I would wish for a better name, though...
>> 
>> -Thorsten
>> 
>> 
>>> 
>>>> On Jan 25, 2016, at 1:43 PM, Félix Cloutier <felixcca at yahoo.ca <mailto:felixcca at yahoo.ca>> wrote:
>>>> 
>>>> There would be no difference at all between local and private if you had one class per file. It seems very very bold to me to say that Swift "doesn't support encapsulation" but that local would solve that problem.
>>>> 
>>>> Félix
>>>> 
>>>>> Le 25 janv. 2016 à 13:16:45, Ilya Belenkiy via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> a écrit :
>>>>> 
>>>>>> A language does not need to have strict access controls in order to be considered OO. 
>>>>> 
>>>>> This is a matter of terminology. It still doesn’t change the fact that data encapsulation is a fundamental feature of object oriented programming that is currently not supported.
>>>>> 
>>>>>> You don’t even need “classes” to do OO either.
>>>>> 
>>>>> In this terminology C is also object oriented. You can have opaque pointers to structs with functions around them. Swift current support for data encapsulation is exactly like that. But people don’t do this kind of programming in C precisely because the compiler can provide a lot more help than this.
>>>>> 
>>>>>> This really seems like an academic problem vs a pragmatic problem. 
>>>>> 
>>>>> 
>>>>> It’s very pragmatic. With properly marked access level and well designed interfaces, the class implementor may rely on the compiler to ensure that the class invariants / internal state will not become corrupt. Without it, the code is much more likely to break due to human error. It’s the same reasoning as with having ARC rather than doing manual retain / release and having destructors that are called automatically instead of calling cleanup code manually.
>>>>> 
>>>>>> There’s also no concept of “friend” in Swift either
>>>>> 
>>>>> file based access level is a good solution for this. But it’s not a solution at all for real data encapsulation.
>>>>> 
>>>>>> On Jan 25, 2016, at 12:09 PM, David Owens II <david at owensd.io <mailto:david at owensd.io>> wrote:
>>>>>> 
>>>>>> 
>>>>>>> On Jan 25, 2016, at 4:47 AM, Ilya Belenkiy via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>>>> 
>>>>>>>> Data encapsulation is indeed one of the cornerstone of OO, but every design decision is a trade-off. Is Python not object-oriented because they lack a private keyword, and have the convention of marking internal items with a leading underscore?
>>>>>>> 
>>>>>>> 
>>>>>>> Then Python has the same problem. A language that *supports* OOP should not leave such an important part of OOP to coding by convention. 
>>>>>> 
>>>>>> I think this where you are being lead astray. A language does not need to have strict access controls in order to be considered OO. Languages like C#, Java, and to some extent, C++ tend to make people think this. You don’t even need “classes” to do OO either.
>>>>>> 
>>>>>>>> The best anyone can do is make the breaking of encapsulation an explicit choice. I’m intuiting that you think that writing code into the file where the class was defined is not explicit enough.
>>>>>>> 
>>>>>>> Right now, it’s impossible to make the distinction: is something truly private or can be used safely in the same file? The language has no way of expressing it. The class internal state is not encapsulated outside the bounds of the class.
>>>>>> 
>>>>>> This really seems like an academic problem vs a pragmatic problem. There’s also no concept of “friend” in Swift either, which is another construct that would have be invented to allow the “private” things to be used by others elsewhere. 
>>>>>> 
>>>>>> -David
>>>>> 
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>> 
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160126/a8174107/attachment.html>


More information about the swift-evolution mailing list