> On Feb 25, 2017, at 4:01 PM, Jonathan Hull <[email protected]> wrote: > >> >> On Feb 25, 2017, at 1:19 PM, Matthew Johnson <[email protected] >> <mailto:[email protected]>> wrote: >>> On Feb 25, 2017, at 2:54 PM, Jonathan Hull via swift-evolution >>> <[email protected] <mailto:[email protected]>> 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. > , 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). > 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. 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. > 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. > > Thanks, > Jon > > > >>> >>> Thanks, >>> Jon >>> >>> >>> >>>> On Feb 23, 2017, at 1:56 PM, Nevin Brackett-Rozinsky via swift-evolution >>>> <[email protected] <mailto:[email protected]>> wrote: >>>> >>>> Introduction >>>> >>>> There has been a deluge of discussion about access levels lately, all >>>> attempting to simplify the situation. Shortly after Swift 3 was released, >>>> many people realized that the new access modifier story was far more >>>> complex than the old one, and expressed the belief that the changes may >>>> have been a mistake. >>>> >>>> In the months that followed, more and more people came to share the same >>>> view, and stage 2 of Swift 4 has seen a profusion of proposals addressing >>>> the issue. These proposals are generally small and focus on changing just >>>> one aspect of access control. However, given the situation we are in now, >>>> it is important to look at the problem in its entirety and arrive at a >>>> cohesive solution. >>>> >>>> Background >>>> >>>> During the Swift 3 timeframe there were lengthy debates about access >>>> control. The end results were to adopt SE-0025 >>>> <https://github.com/apple/swift-evolution/blob/master/proposals/0025-scoped-access-level.md>, >>>> which introduced the ‘fileprivate’ keyword and made ‘private’ >>>> scope-based, and SE-0117 >>>> <https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md>, >>>> which made ‘public’ classes closed by default and introduced the ‘open’ >>>> keyword. At the time, there was broad agreement (and some dissent) that >>>> these were the right changes to make. >>>> >>>> That belief, as well as the numerous arguments which led to it, were >>>> well-informed and thoughtfully considered. However, due to the inevitable >>>> linear nature of time, they were not based on first-hand experience with >>>> the new changes. Upon the release of Swift 3, we all gained that >>>> first-hand experience, and it quickly became apparent to many people that >>>> the new access control system was needlessly complicated, and not at all >>>> the improvement it had been heralded as. >>>> >>>> Rather than make it easy to encapsulate implementation details of related >>>> types across multiple files, we had instead doubled down on requiring that >>>> many things go in a single file or else reveal their secrets to the entire >>>> module. Even worse, the new scope-based ‘private’ discouraged the >>>> preferred style of using extensions to build up a type. To cap it off, we >>>> went from needing to know two access modifier keywords (‘public’ and >>>> ‘private’) to needing to know four of them (‘private’, ‘fileprivate’, >>>> ‘public’, and ‘open’) without even providing a way to share details across >>>> a small number of related files. >>>> >>>> Motivation – Overview >>>> >>>> Many different ideas for access control have been expressed on the Swift >>>> Evolution mailing list. Some people want ‘protected’ or ‘friend’ or >>>> ‘extensible’ or various other combinations of type-based visibility. Other >>>> people (*cough* Slava) see no need for any levels finer than ‘internal’ at >>>> all. The common points of agreement, though, are that ‘fileprivate’ is an >>>> awkward spelling, and that the whole system is too complicated. >>>> >>>> It is important that we as the Swift community are able to recognize our >>>> mistakes, and even more important that we fix them. We originally thought >>>> that the Swift 3 access control changes would be beneficial. However, >>>> experience with them in practice has shown that not to be the case. >>>> Instead, the language became more complex, and that has real costs. It is >>>> time for a simplification. >>>> >>>> The prevailing view from recent discussions is that there should be just >>>> one access level more fine-grained than ‘internal’, and it should be >>>> spelled ‘private’. Let us leave aside for the moment what its exact >>>> meaning should be, and consider the other end of the scale. >>>> >>>> Motivation – Rethinking ‘public’ >>>> >>>> Prior to Swift 3 we had just one access level more broad than ‘internal’, >>>> and for simplicity of the model it would be desirable to achieve that >>>> again. However, SE-0117 raised the point that certain library designs >>>> require a class to have subclasses within the defining module, but not >>>> outside. In other words, client code should not be able to create >>>> subclasses, even though they exist in the library. Let us be clear: this >>>> is a niche use-case, but it is important. >>>> >>>> The most common situations are that a class either should not be >>>> subclassable at all—in which case it is ‘final’—or that it should be >>>> subclassable anywhere including client code. In order for a library to >>>> need a publicly closed class, it must first of all be using classes rather >>>> than a protocol with conforming structs, it must have a hierarchy with a >>>> parent class that is exposed outside the module, it must have subclasses >>>> of that parent class within the module, and it must also require that no >>>> external subclasses can exist. Putting all those criteria together, we see >>>> that closed classes are a rare thing to use. Nonetheless, they are >>>> valuable and can enable certain compiler optimizations, so we should >>>> support them. >>>> >>>> Currently, the spelling for a closed class is ‘public’. This makes it very >>>> easy for library authors to create them. However, since they are a niche >>>> feature and most of the time ‘final’ is a better choice, we do not need to >>>> dedicate the entire ‘public’ keyword to them. >>>> >>>> Moreover, object-oriented programming is just as much a first-class >>>> citizen in Swift as protocol-oriented programming is, so we should treat >>>> it as such. Classes are inherently inheritable: when one writes “class Foo >>>> {}”, then Foo has a default visibility of ‘internal’, and by default it >>>> can have subclasses. That is a straightforward model, and it is easy to >>>> work with. >>>> >>>> If subclasses are to be disallowed, then Foo should be marked ‘final’; if >>>> Foo is exported to clients then it should be marked ‘public’; and if both >>>> are true then Foo should be ‘public final’. This covers all the common >>>> cases, and leaves only the narrow corner of closed classes to consider. >>>> Per the motivation of SE-0117, that case is worth handling. Per our >>>> collective experience with Swift 3, however, it is not worth the added >>>> complexity of its own access modifier keyword. We need a better way to >>>> spell it. >>>> >>>> One of the reasons ‘public’ was previously chosen for closed classes is to >>>> provide a “soft default” for library authors, so they can prevent >>>> subclassing until they decide later whether to allow it in a future >>>> release. This is a misguided decision, as it prioritizes the convenience >>>> of library authors over the productivity of application developers. >>>> Library authors have a responsibility to decide what interfaces they >>>> present, and we should not encourage them to release libraries without >>>> making those decisions. >>>> >>>> Moreover, we need to trust client programmers to make reasonable choices. >>>> If a library mistakenly allows subclassing when it shouldn’t, all a client >>>> has to do to work with it correctly is *not make subclasses*. The library >>>> is still usable. Conversely, if a library mistakenly prohibits >>>> subclassing, then there are things a client *should* be able to do but >>>> cannot. The harm to the users of a library is greater in this last case, >>>> because the ability to use the library is compromised, and that diminishes >>>> their productivity. >>>> >>>> We should not make “soft defaults” that tend to negatively impact the >>>> clients of a library for the dubious benefit of enabling its author to >>>> procrastinate on a basic design decision. If someone truly wants to >>>> publish a library with a closed class, then we should support that. But it >>>> should be an intentional decision, not a default. >>>> >>>> Motivation – Rethinking ‘final’ >>>> >>>> The question then comes to spelling. It is evident that preventing >>>> subclasses is closely related to being ‘final’. One possibility, then, is >>>> to allow the ‘final’ keyword to take a parameter. The parameter would be >>>> an access level, to indicate that the type acts like it is final when >>>> accessed from at or above that level. >>>> >>>> In particular, ‘final(public)’ would mean “this class cannot be subclassed >>>> from outside the module”, or in other words “this class appears final >>>> publicly, although it is nonfinal internally”. This approach is more >>>> powerful than a ‘closed’ keyword because it also allows ‘final(internal)’, >>>> meaning “this class appears final to the rest of the module, although it >>>> can be subclassed privately”. >>>> >>>> Motivation – Rethinking ‘private’ >>>> >>>> Now let us return to ‘private’, which as discussed earlier should be the >>>> only modifier that is tighter than ‘internal’. The purpose of ‘private’ is >>>> to enable encapsulation of related code, without revealing implementation >>>> details to the rest of the module. It should be compatible with using >>>> extensions to build up types, and it should not encourage overly-long >>>> files. >>>> >>>> The natural definition, therefore, is that ‘private’ should mean “visible >>>> in a small group of files which belong together as a unit”. Of course >>>> Swift does not yet have submodules, and is not likely to gain them this >>>> year. However, if we say that each file is implicitly its own submodule >>>> unless otherwise specified, then the model works. In that view, ‘private’ >>>> will mean “visible in this submodule”, and for the time being that is >>>> synonymous with “visible in this file”. >>>> >>>> Although this does not immediately enable lengthy files to be separated >>>> along natural divisions, it does lay the groundwork to allow doing so in >>>> the future when submodules arrive. >>>> >>>> Motivation – Summary >>>> >>>> By looking at access control in its entirety, we can adopt a system that >>>> empowers both library authors and client programmers to organize their >>>> code in a principled way, and to expose the interfaces they want in the >>>> places they need. The complexity of the Swift 3 visibility story, which >>>> many people now regret creating, will be replaced by a far simpler model >>>> which in several respects is even more powerful. >>>> >>>> Notably, being able to parameterize ‘final’ lets classes be closed not >>>> just externally, but also in the rest of the module outside the ‘private’ >>>> scope if desired. Furthermore, defining ‘private’ as being scoped to a >>>> group of related files means that, as soon as we get the ability to create >>>> such groups, it will no longer be necessary to write large files just to >>>> keep implementation details hidden. >>>> >>>> Recommendations >>>> >>>> To recap, the ideas presented here focus on simplifying access control >>>> while still supporting important use cases such as closed class >>>> hierarchies. The indicated design uses just three familiar access keywords: >>>> >>>> ‘private’, to restrict visibility to a group of files, or just one file >>>> until we get that capability. >>>> >>>> ‘internal’, which is the default and does not have to be written, for >>>> module-wide visibility. >>>> >>>> ‘public’, to make visible outside the module, including the ability to >>>> subclass. >>>> >>>> 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. >>>> >>>> Conclusion >>>> >>>> The Swift 3 access situation is harmful—as evidenced by the myriad calls >>>> to fix it—not just because of its excessive complexity, but also because >>>> it prioritizes convenience for library authors over utility for their >>>> clients, and because it has no natural way to accommodate splitting large >>>> files into smaller ones while preserving encapsulation. >>>> >>>> We have an opportunity now to correct a mistake we have made, and to set a >>>> precedent that we *will* correct our mistakes, rather than continue down >>>> an undesirable path simply because it seemed like a good idea at the time. >>>> When real-world experience demonstrates that a change has taken us in the >>>> wrong direction, we can and should update our decisions based on that new >>>> experience. >>>> >>>> Therefore, in the situation at hand, we should reconsider our access >>>> modifier story and choose a model which is both simple and powerful. I >>>> have presented here my best efforts at describing such a system, and I >>>> offer it as one possible way to move forward. >>>> >>>> >>>> – Nevin >>>> _______________________________________________ >>>> swift-evolution mailing list >>>> [email protected] <mailto:[email protected]> >>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>> <https://lists.swift.org/mailman/listinfo/swift-evolution> >>> >>> _______________________________________________ >>> swift-evolution mailing list >>> [email protected] <mailto:[email protected]> >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
