Some very good points. But I’ll concentrate on what I disagree on to see where 
we can go from there.

> On 23 Feb 2017, at 22:56, Nevin Brackett-Rozinsky via swift-evolution 
> <[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.

The whole design and motivation behind SE-0117 is that it is not rare to want 
classes to be freely subclass-able internally while being closed to subclassing 
externally. Do you have arguments to contradict the lengthy points mentioned in 
the SE-0117 motivation section? The reasons for the rarity above do not make 
much sense to me:

"it must first of all be using classes rather than a protocol with conforming 
structs”: we are discussing the merits of the different forms of subclassing 
prevention (public vs final), so I don’t see the reason for mentioning structs.
"it must have a hierarchy with a parent class that is exposed outside the 
module”: I don’t see why it requires a hierarchy. You might want the 
soft-default of public to apply to non-subclassed classes which you might 
subclass in a later version of the library.

> 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.

That’s a bit harsh. For me, it has nothing to do with procrastination, but with 
being safe-by-default, which is an important Swift goal. You are arguing for 
useful-by-default, which is only a different priority.

> 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”.

As you can guess, I’m not a fan of this change :) I know that final has a weird 
place in the language now, but I’m relatively happy with the public/open design.

> 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”.

This is very counterintuitive for me. I’m a proponent for going back to a 
file-based private but your solution has two big disadvantages for me:

it makes impossible to have a file-based scope in submodules
private would have different semantics depending if its in a submodule or not. 
As a consequence:
it would be confusing to learn and use
it would make copy-pasting/moving files from a submodule to a top-module 
potentially breaking:

x.swift in submodule A
class X {
    private init() {}
}

y.swift in submodule A
class Y {
    let x = X()
}

This above code would stop working when moved to a top-module scope because X’s 
initializer would become un-accessible to Y. That’s just one example, but I’d 
really like to stress that changing the scope of an access-level depending on 
the submodule context seems like a really bad idea to me.

> 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]
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to