[swift-evolution] A Comprehensive Rethink of Access Levels in Swift

Matthew Johnson matthew at anandabits.com
Mon Feb 27 17:18:03 CST 2017


> On Feb 26, 2017, at 10:47 PM, Jonathan Hull <jhull at gbis.com> wrote:
> 
>> 
>> On Feb 25, 2017, at 2:41 PM, Matthew Johnson <matthew at anandabits.com <mailto:matthew at anandabits.com>> wrote:
>> 
>>> 
>>> On Feb 25, 2017, at 4:01 PM, Jonathan Hull <jhull at gbis.com <mailto:jhull at gbis.com>> wrote:
>>> 
>>>> 
>>>> On Feb 25, 2017, at 1:19 PM, Matthew Johnson <matthew at anandabits.com <mailto:matthew at anandabits.com>> wrote:
>>>>> On Feb 25, 2017, at 2:54 PM, Jonathan Hull via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>> +1000
>>>>> 
>>>>> This is the best of the proposals I have seen.  I think it works by itself as a complete proposal, but since we are talking "comprehensive rethink", there is one use case which most of the proposals seem to miss.  
>>>>> 
>>>>> That is, what if I want to make a class which is both Public and Subclassable, but I want to keep some of the implementation details private to the class, while still allowing subclasses and extensions (across the module boundary) to have access to those details.  A concrete example of this is UIGestureRecognizer, which is intended to be subclassed outside of its framework, but hides potentially catastrophic things (like being able to set the state) from callers. This isn’t currently possible in Swift AFAICT without importing from ObjC.
>>>>> 
>>>>> My solution to this has been to allow something to be marked ‘public hidden’ or ‘hidden', which means that it is public/internal, but has it’s visibility limited only to files which opt-in to seeing it with ‘import hidden filename’ (instead of just ‘import filename’).  I think this is the simplest way to provide this feature, which is sometimes very necessary. It also captures authorial intent very nicely.
>>>> 
>>>> My submodule proposal is able to support this use case.  You would just put the symbols intended for subclasses in a separate submodule.  These submodules could be exposed to users with any name you desire, regardless of the internal structure of your module.  You could even arrange for users to get all of the non-subclass-only symbols automatically when they import your module but require explicit import of each individual subclass-only submodule in places where it is needed.
>>> 
>>> I agree that this could also be solved with nested submodules
>> 
>> A minor note, but you would not need to nest them.  You could do it either way.
> 
> I can’t think of how to do this, but I trust you that it can.
> 
> 
>>> , but the current proposals all seem to add a lot of complexity. This complexity does give you much finer grain control over visibility, but what I like about Nevin’s proposal is that it is ‘just enough’ control with minimal complexity.  
>> 
>> Determining what “just enough” is is obviously a very subjective thing.  :)  Everyone seems to have their own opinion about this (more than usual) which is going to make it one of the more complicated discussions we’ll have on the list.
>> 
>> I worked very hard on my proposal to try to design a system that stays out of your way until you need the tools it offers.  That way nobody is faced with complexity unless they are deriving direct benefit from it, but when they need powerful tools those tools are available.  This is the idea of progressive disclosure (as I understand it).
> 
> True.  I would personally define “just enough” as easy for the 80% and possible for the 98%.
> 
> I don’t think your proposal is bad at all.  I just like the simplicity of Nevin’s a little more (especially with the confusion around ‘open’). In fact, I would like to see you combine them a bit (see below).
> 
> 
>>> What I like about ‘hidden’ vs export of nested submodules is that you can freely intermix those declarations in your code (like you can with private now).
>> 
>> Yeah, this is an advantage.  It’s not a bad idea, but it’s not a replacement for submodules either.  They are complementary.  
> 
> Agreed.
> 
> What I would really like to see is Nevin’s proposal with a simplified version of yours providing the submodules, and something like ‘hidden’ replacing the idea of ‘export’.

Are you saying you want to see all submodules exposed externally automatically?  Or are you only talking about non-top-level export?

> 
> In more detail, I really like just having ‘private’, ‘internal’, and ‘public’ where private means private to the submodule.  It is very simple and understandable.  I think having a single level of submodule using your ‘submodule’ syntax is the simplest way to provide those.  

I think there is going to be *a lot* of resistance to the idea of removing file-level private.  Most people want to keep that, but just revert than name to `private` like we had in Swift 2. 

> 
> I think I would be ok with nested submodules as well, again using your syntax, as long as we have ‘private’ mean private within the current submodule (and children).  If we find we really do need parametrized private (or equivalent) to provide visibility to a specific parent scope, that is something that can be an additive (non-breaking) change later.  I think we will be able do pretty much everything we need to without it though.

I could be wrong, but I’d be willing to bet that in a submodule world you would have a very vocal and large number of people complaining if we suggest not including any of: module-wide visibility, submodule-wide visibility and file-wide visibility.  There are certainly people who think that one or another of these is not worth supporting but I don’t see anything near a consensus around avoiding any one of these.  Each will have a very vocal group of proponents that I believe (again I could be wrong) would be a majority with respect to that particular scope.  

I can understand why somebody might prefer a style that avoids relying one of these scopes (people have their idiosyncrasies after all).  What I have a hard time understanding is why somebody would propose that *nobody* be able to scope a symbol to one of these very physical and prominent scopes.

> 
> 
> 
>> One thing `hidden` doesn’t do is allow you to say *why* the symbols are hidden and make that clear when they are imported.  I can imagine allowing it to take a parameter that is a user-defined identifier stating *why* the symbols are hidden.  For example, I might have `hidden(subclass)` and `hidden(extension)`.  Making it a user-defined identifier would encourage names that mean something, require users to have read the documentation telling them what the identifier is, and make it clear to readers of the code why hidden symbols are imported (if the identifier used does not match any available symbols it would result in a compiler error).  
>> 
>> A user-defined identifier would also be better than a small set of language-defined arguments because users would expect language-defined identifiers like “subclass” or “extension” to be *enforced* by the language.  There has been strong pushback against doing that for several reasons.  A parameterized `hidden` is a general feature that could express what is desired very well while not over-promising and under-delivering  `protected` or `typeprivate` are extremely permeable restrictions and therefore don’t offer any real guarantees that `hidden` or a factored out submodule wouldn’t offer.  It’s better to have a more general feature that does exactly what it says and leads users to believe it does.
> 
> I disagree.  The whole point of ‘hidden’ is to protect the *callers* of a type from accidentally invoking its deliberate extension points.  It basically means “don’t touch these unless you understand them”, and having to ‘import hidden’ is explicitly taking responsibility for this. I don’t think making extenders type a secret code would actually help anything, and would just annoy/complicate. There is only so much you can do to protect people from themselves.
> 
> Right now, anything that would be marked ‘public hidden’ has to be marked ‘public’, so I think it is a thorough improvement over the status quo.

That’s a reasonable perspective.  This feature seems like it could certainly be useful in some cases but it’s applicable in relatively narrow cases and is orthogonal to the things I am interested in focusing on right now so I don’t plan to propose it.  I’m happy to wait and see if somebody else decides to run with it.

> 
>> 
>>>  There is also the question of how to separate a single type into multiple submodules, when we can’t declare storage in an extension.
>> 
>> We have discussed allowing storage to be declared in an extension in the past on the list.  There is reasonable support for adding this eventually and we have ideas about how to do it (the complexity is primarily around insuring proper initialization of storage declared in extensions).  Introducing a submodules feature could well drive demand for it by demonstrating concrete use cases where it becomes more necessary.
>> 
>> Even without that feature it is possible to separate a type into separate submodules using the features in my proposal.  The details of how you do that would depend on concrete use cases.
> 
> True, and I do want this feature… but I don’t want to rely on it being there when it is not yet on the roadmap.
> 
> Thanks,
> Jon

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


More information about the swift-evolution mailing list