[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