[swift-evolution] [Review] SE-0159: Fix Private Access Levels
Vladimir.S
svabox at gmail.com
Thu Mar 30 05:39:22 CDT 2017
[offtopic]
Just wonder, if I missed the resolution for SE-0159? (although I even
checked the list's archive). There was IMO a very low activity in the list
in last couple of days, so I decided to ask if probably there was a problem
with mailing server etc..
[/offtopic]
On 28.03.2017 17:36, Vladimir.S wrote:
> On 28.03.2017 1:33, Xiaodi Wu wrote:
>> Note that allowing nested extensions will require breaking rules for access
>> modifiers on all extensions. This is because, during revisions for SE-0025,
>> the idea of aligning rules for extensions to rules for types was brought up
>> and rejected. Subsequent proposals to do so also failed at various stages.
>>
>> The current rule for types is as follows: the visibility of the type is the
>> upper limit for visibility of its members. Members have an implicit access
>> level of internal but may state any other access level, even if higher than
>> the containing type, and the _effective_ access level will be restricted by
>> the containing type. For example: `internal struct S { public var i: Int }`
>> has a member that is _effectively_ internal.
>>
>> The current rule for extensions is as follows: although extensions are a
>> scope for the purposes of "new private", they are not a first-class entity
>> and have no existence in the type system. As such, they cannot be the upper
>> limit for members. Instead, the access modifier for an extension is unique
>> in Swift: it is regarded as a "shorthand" for the default access modifier
>> for contained members and also the maximum permitted access level for those
>> members; for example, inside a "public extension" the members default to
>> public (not internal). Attempts to remove this behavior were fiercely
>> rejected by those who want to use the shorthand to have an extension full
>> of public members without restating "public".
>>
>> If, however, it is a "private extension", then the default access modifier
>> for members in that extension is "fileprivate". This is correct *only* if
>> extensions can never be nested. There is no spelling for the maximum
>> possible visibility of a member inside a _nested_ private extension, and it
>> is not permitted to state an explicit access level greater than that of the
>> extension itself. If you want to support nested extensions, then this rule
>> cannot continue. However, previous attempts at harmonizing these rules were
>> not successful.
>
> OK, I see your point, thank you for detailed description.
>
> First, probably (as was discussed in this thread) we should disallow
> 'scoped' access level(current 'private') at top level of file. This IMO
> will remove one of the big confusion point : 'scoped' will be allowed only
> inside a scope less than file.
> So, in this case, you'll have no "private extension"(current 'private') -
> only "fileprivate extension" at top level.
>
> (I'll use 'scoped' and 'fileprivate' below to be clear what I mean.
> Currently they are 'private' and 'fileprivate', and probably will be
> 'scoped' and 'private')
>
> Second - "There is no spelling for the maximum possible visibility of a
> member inside a _nested_ private extension".. I.e. member with even
> 'public' access modifier in such extension will be effectively 'scoped' and
> so nothing can be used outside of the extension ? If I understood correctly
> - then it seems like we should just not allow 'scoped' for nested
> extensions as such extension just has no sense.
>
> So, both top level extensions and nested would have only 'fileprivate',
> 'internal' or 'public'. 'scoped' is not allowed for them(if we accept the
> above suggestions):
>
> class MyType { // internal
> public var a = 10 // internal(because of type's access level)
> var b = 20 // internal
> scoped var c = 30 // scoped for type and for nested extensions
>
> // default and max level is fileprivate, bounded by type's access level
> fileprivate extension MyProtoA {
> scoped var d = 10 // scoped for extension, inaccessible outside of it
>
> var e = 20 // fileprivate
> internal var f = 20 // fileprivate
> public var g = 30 // fileprivate
> }
>
> // default and max level is internal, bounded by type's access level
> extension MyProtoB {
> scoped var h = 40 // scoped for extension, inaccessible outside of it
>
> var i = 50 // internal
> internal var j = 60 // internal
> public var k = 70 // public->internal(type's access level)
> }
>
> // default and max level is public, bounded by type's access level
> public extension MyProtoC {
> scoped var l = 80 // scoped for extension, inaccessible outside of it
>
> var m = 90 // public -> internal(type's access level)
> internal var n = 100 // internal
> public var o = 110 // public->internal(type's access level)
> }
> }
>
> Do you see any drawbacks in such design? (given that we disallow scoped
> access modifier at top level of file and for nested extensions in type)
>
>
>> On Mon, Mar 27, 2017 at 15:59 Vladimir.S via swift-evolution
>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>
>> On 27.03.2017 20:00, Ross O'Brien via swift-evolution wrote:
>> > I'm considering this from a different angle.
>> >
>> > When we declare a type, we declare properties and functions which
>> that type
>> > has.
>> > When we extend a type, we add functions to the type. (I'm including
>> > computed properties in this.)
>> > It's become an idiom of Swift to declare an extension to a type for
>> each
>> > protocol we want it to conform to, for reasons of code organisation
>> and
>> > readability. This may be true even if conformance to the protocol
>> was a
>> > primary intent of creating the type in the first place.
>> >
>> > The intent of the scoped access level is to allow programmers to
>> create
>> > properties and functions which are limited to the scope of their
>> > declaration. A protocol conformance can be written, with the aid of
>> helper
>> > functions, in the confidence that the helper functions are not visible
>> > outside the extension, minimising their impact on other components
>> of the
>> > module.
>> > However, some protocol conformances require the type to have a
>> specific
>> > property, which the extension cannot facilitate. Some protocol
>> conformances
>> > don't require a property, but it would be really useful to have
>> one, and
>> > again an extension can't facilitate.
>> >
>> > Example: we want to be able to write this, but we can't:
>> >
>> > privateprotocolBar
>> >
>> > {
>> >
>> > varinteger : Int{ get}
>> >
>> > funcincrement()
>> >
>> > }
>> >
>> >
>> > structFoo
>> >
>> > {
>> >
>> > }
>> >
>> >
>> > extensionFoo: Bar
>> >
>> > {
>> >
>> > varinteger : Int
>> >
>> >
>> > privatevarcounter : Int
>> >
>> > funcincrement()
>> >
>> > {
>> >
>> > counter += 1
>> >
>> > }
>> >
>> > }
>> >
>> >
>> > This leads to a workaround: that properties are added to the original
>> type,
>> > and declared as fileprivate. They're not intended to be visible to any
>> > scope other than the conforming extension - not even, really, to the
>> type's
>> > original scope.
>> >
>> > Continuing the example: we've compromised and written this:
>> >
>> > privateprotocolBar
>> >
>> > {
>> >
>> > varinteger : Int{ get}
>> >
>> > funcincrement()
>> >
>> > }
>> >
>> >
>> > structFoo
>> >
>> > {
>> >
>> > fileprivatevarinteger : Int
>> >
>> > fileprivatevarcounter : Int
>> >
>> > }
>> >
>> >
>> > extensionFoo: Bar
>> >
>> > {
>> >
>> > funcincrement()
>> >
>> > {
>> >
>> > counter += 1
>> >
>> > }
>> >
>> > }
>> >
>> >
>> > This is not a fault of fileprivate (though it's a clunky name), or
>> private.
>> > Renaming these levels does not solve the problem. Removing private,
>> such
>> > that everything becomes fileprivate, does not solve the problem. The
>> > problem is in the extension system.
>> > (At this point I realise I'm focusing on one problem as if it's the
>> only one.)
>> >
>> > Supposing we approached extensions differently. I think around
>> SE-0025 we
>> > were considering a 'nested' access level.
>> >
>> > Supposing we created a 'conformance region' inside a type
>> declaration - a
>> > scope nested within the type declaration scope - and that this
>> conformance
>> > region had its own access level. It's inside the type declaration, not
>> > separate from it like an extension, so we can declare properties
>> inside it.
>> > But literally the only properties and functions declared inside the
>> region
>> > but visible anywhere outside of it, would be properties and functions
>> > declared in the named protocol being conformed to.
>> >
>> > So, visually it might look like this:
>> >
>> >
>> > privateprotocolBar
>> >
>> > {
>> >
>> > varinteger : Int{ get}
>> >
>> > funcincrement()
>> >
>> > }
>> >
>> >
>> > structFoo
>> >
>> > {
>> >
>> > conformance Bar // or conformance Foo : Bar, but since the region is
>> > inside Foo that's redundant
>> >
>> > {
>> >
>> > varinteger : Int // visible because Foo : Bar, at Bar's access level
>> >
>> >
>> > varcounter : Int = 0// only visible inside the conformance scope,
>> because
>> > not declared in Bar
>> >
>> >
>> > funcincrement() // visible because Foo : Bar, at Bar's access level
>> >
>> > {
>> >
>> > counter += 1
>> >
>> > }
>> >
>> > }
>> >
>> > }
>> >
>> >
>> > I've introduced a new keyword, conformance, though it may be clear
>> enough
>> > to keep using extension inside a scope for this. Foo still conforms
>> to Bar,
>> > in the same file. We've removed 'extension Foo :' and moved a '}' for
>> this,
>> > but that's not a breaking change as this is an addition.
>> Readability is
>> > compromised to the extent that this conformance is indented one level.
>> >
>> > I've not long had the idea. It's a different approach and may be
>> worth a
>> > discussion thread of its own for - or someone might point out some
>> > glaringly obvious flaw in it. If it's useful, I don't know the full
>> > implications this would have, such as how much this would reduce
>> the use
>> > of fileprivate (e.g. to none, to the minimal levels expected in
>> SE-0025, or
>> > no effect at all). It's just intended to remove a problem
>> > which fileprivate is applied as a bad workaround for.
>>
>> IMO It seems like really great suggestion - I'd like to discuss it in
>> separate thread, so please start it if you are interested in this.
>>
>> Currently I think the proposal should allow extensions(not new keyword)
>> inside type declaration, and such extensions should be allowed to to
>> have
>> stored properties and theirs 'scoped' members should be inaccessible
>> outside such extension declaration, but such extensions should be
>> allowed
>> to access 'scoped' members of class (because such extension is
>> defined in
>> the same scope)
>>
>> I.e.
>>
>> class MyClass {
>> scoped var foo = 10
>>
>> extension ProtoA {
>> scoped var bar = 20 // visible only in this extension(scope)
>> var baz = 30 // will be accessible as 'normal' property of MyClass
>> func barFunc() { print(bar); print(foo); } // can access foo
>> }
>>
>> extension ProtoB {
>> scoped var bar = 40 // visible only in this extension(scope)
>> //var baz = 50 // can't be re-definied here
>> func anotherFunc() { print(baz); print(foo); } // can access baz
>> }
>> }
>>
>> I see such benefits of this:
>> * We leave current model of extensions "as is", no changes for
>> extensions
>> declared outside of the type.
>> * IMO we need stored properties in extensions at least in the same file
>> with type declaration(as was shown by discussion in the list) - and the
>> proposed solution gives them to us
>> * IMO this solution is better than just allow stored properties in
>> extensions in the same file:
>> * in the latter case extension has no access to 'scoped' members
>> of type
>> but this can be very useful to implement protocol conformance with
>> help of
>> internal(scoped) details, that should not be exposed to whole file
>> level.
>> * with proposed solution it is clear that any memeber declared in
>> extension block is a native part of type. We see all members in the same
>> type declaration.
>> * proposed solution has a simple mental model - "all that is defined
>> inside type declaration is naturally a part of that type, 'scoped'
>> access
>> 'uses' same rules as usually".
>>
>> Vladimir.
>>
>> >
>> > Ross
>> >
>> >
>> > On Mon, Mar 27, 2017 at 4:26 PM, Rien via swift-evolution
>> > <swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> <mailto:swift-evolution at swift.org <mailto:swift-evolution at swift.org>>>
>> wrote:
>> >
>> >
>> > > On 27 Mar 2017, at 16:46, Steven Knodl via swift-evolution
>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> <mailto:swift-evolution at swift.org <mailto:swift-evolution at swift.org>>>
>> wrote:
>> > >
>> > > Late to the party here
>> > >
>> > > * What is your evaluation of the proposal?
>> > > I’m -1 on this. The proposal is not a difficult read, but may
>> have been simply more simply named “Remove Scoped Access Level/Revert
>> SE-0025” as that is what is being proposed. “Fix” seems to me to be a
>> unfortunately worded judgmental proposal title.
>> > >
>> > > * Is the problem being addressed significant enough to warrant
>> a change?
>> > > No. I consider myself to be a fairly “new/n00b” Obj-C/Swift
>> developer. Although SE-0025 was a change that required source changes
>> when implemented, the reasoning was not difficult to understand and
>> changes were simple to make once identified. Here they are from
>> SE-0025
>> > >
>> > > • public: symbol visible outside the current module
>> > > • internal: symbol visible within the current module
>> > > • fileprivate: symbol visible within the current file
>> > > • private: symbol visible within the current declaration
>> > >
>> > > Moving forward these changes are not difficult to comprehend.
>> I tend to make _everything_ “private” up front so I don’t have any API
>> leakage. Then dial back to “fileprivate” as needed. It’s not
>> difficult for me I guess.
>> >
>> > Right. I do that myself more than I would like to admit.
>> > But when we only loosen up/tighten down during coding then access
>> > levels are almost useless.
>> > The point of access level control is in the design, not in the
>> coding.
>> > If we made a design (including access levels) and then have to
>> dial
>> > back, that should be a warning that something is wrong.
>> > To me, this is an argument in favour of the proposal.
>> >
>> > Rien.
>> >
>> >
>> >
>> > > As such, I don’t believe that this change was “Actively
>> Harmful”,
>> > especially for new developers who have a clean slate or simply are
>> > leaving everything unmarked (internal) anyhow until they move
>> up to
>> > more advanced topics. Unraveling a generic or functional code
>> someone
>> > else wrote uses way more cognitive power.
>> > >
>> > > I’d like to address the suggestion that the migration for
>> SE-0159
>> > could “simply” be a search and replace without loss of
>> functionality.
>> > This doesn’t make sense if you consider the entire code lifecycle.
>> > Sure the code gets migrated and compiles. This is fine if they
>> code
>> > _never_ has to be read again. But as we know, code is written
>> once and
>> > _read many times_ as it will need to be maintained. The
>> distinction
>> > between private and fileprivate contains information, and
>> although it
>> > may work correctly now, some information meant to help maintain
>> that
>> > code has been lost if these keywords are merged and the
>> functionality
>> > of scoped access is removed. So yes if you don’t maintain the
>> code
>> > where this migration takes place, this would be ok. But Swift
>> strives
>> > for readability. Moving classes to separate files to address
>> these
>> > issues, creates a multitude of Bunny classes where again for
>> > readability some classes belong together in the same file for
>> ease of
>> > comprehension (again, code is written once , read many times)
>> > >
>> > > * Does this proposal fit well with the feel and direction of
>> Swift?
>> > > The spirit of the proposal to simplify access levels is well
>> taken.
>> > This proposal however simplifies at the expense of lost
>> functionality
>> > (Scoped Access levels) with no replacement. The threads talk a
>> about
>> > submodules and other solutions that could fill this gap that are
>> not on
>> > the roadmap, planned or possible which makes them
>> non-admissible in
>> > considering this proposal.
>> > >
>> > > * If you have used other languages, libraries, or package
>> managers
>> > with a similar feature, how do you feel that this proposal
>> compares to
>> > those?
>> > > I am more familiar with scoped access so perhaps that feels more
>> > natural to me. But with the current implementation Swift users
>> can
>> > choose whether they use File Based or Scope Based tools, so
>> although
>> > not ideal to either side, acceptable until a suitable replacement
>> could
>> > be forged.
>> > >
>> > > * How much effort did you put into your review? A glance, a
>> quick
>> > reading, or an in-depth study?
>> > > Re-read SE-0025/proposal/most of this very long thread
>> > >
>> > >
>> > >
>> > >
>> > > From: <swift-evolution-bounces at swift.org
>> <mailto:swift-evolution-bounces at swift.org>
>> > <mailto:swift-evolution-bounces at swift.org
>> <mailto:swift-evolution-bounces at swift.org>>> on behalf of Tino Heth via
>> > swift-evolution <swift-evolution at swift.org
>> <mailto:swift-evolution at swift.org>
>> > <mailto:swift-evolution at swift.org
>> <mailto:swift-evolution at swift.org>>>
>> > > Reply-To: Tino Heth <2th at gmx.de <mailto:2th at gmx.de>
>> <mailto:2th at gmx.de <mailto:2th at gmx.de>>>
>> > > Date: Monday, March 27, 2017 at 6:48 AM
>> > > To: Zach Waldowski <zach at waldowski.me
>> <mailto:zach at waldowski.me> <mailto:zach at waldowski.me
>> <mailto:zach at waldowski.me>>>
>> > > Cc: <swift-evolution at swift.org
>> <mailto:swift-evolution at swift.org> <mailto:swift-evolution at swift.org
>> <mailto:swift-evolution at swift.org>>>
>> > > Subject: Re: [swift-evolution] [Review] SE-0159: Fix Private
>> Access
>> > Levels
>> > >
>> > >
>> > >
>> > >> I am now absolutely thrilled to create a filter to Mark As Read
>> > anything else arising from this thread. Good luck.
>> > >
>> > > That might be a good idea — after more than 200 messages, and a
>> quite
>> > circular discussion with an unhealthy amount of ignorance for the
>> > opposing side ;-).
>> > >
>> > > To fight the latter, I just tried to take the position that "new
>> > private" is really important, and this imho leads to interesting
>> > consequences...
>> > > This access modifier really doesn't solve a problem, like "let"
>> does
>> > (unless the problem you want to solve is having a language with
>> private
>> > access).
>> > > Have a look at this:
>> > >
>> > > public struct SeperateConcerns {
>> > > private var foo: Int = 0
>> > > public mutating func updateFoo(_ value: Int) {
>> > > print("The only allowed way to change foo was
>> invoked")
>> > > foo = value
>> > > }
>> > >
>> > > private var bar: Int = 0
>> > > public mutating func updateBar(_ value: Int) {
>> > > print("The only allowed way to change bar was
>> invoked")
>> > > bar = value
>> > > }
>> > >
>> > > private var foobar: Int = 0
>> > > public mutating func updateFoobar(_ value: Int) {
>> > > print("The only allowed way to change foobar was
>> invoked")
>> > > foobar = value
>> > > }
>> > > }
>> > >
>> > >
>> > > You can protect foo from being changed by code in other
>> files, and
>> > from extensions in the same file — and if the latter is a concern,
>> > there should also be a way to limit access to foo to specific
>> function
>> > in scope.
>> > > Afaik, somebody proposed "partial" type declarations, but
>> without
>> > them, the meaning of private is rather arbitrary, and the
>> feature is
>> > only useful for a tiny special case.
>> > > If we had partial types, the situation would be different,
>> and if
>> > would be possible to declare extensions inside a partial
>> declaration of
>> > another type, we could even remove fileprivate without an
>> replacement
>> > (I guess I should write a separate mail for this thought…)
>> > > _______________________________________________ swift-evolution
>> > mailing list swift-evolution at swift.org
>> <mailto:swift-evolution at swift.org>
>> > <mailto: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>
>> <mailto: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>
>> <mailto: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 <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
More information about the swift-evolution
mailing list