Simplifying Default Access Modifiers


(Jon Hull) #1

I would like to propose a change to the default access modifier within an enclosing scope. The default for top level definitions would stay internal, but anything within a scope would by default have the same visibility as it’s enclosing scope.

The main reason for this is readability/maintainability, and having the intention clearly stand out. It would also reduce a great amount of boilerplate. It also matches the mental model of how scopes normally work regarding inheritance of visibility/properties (which means less to teach newbies).

Right now if I want to make a type and all of it’s vars/methods public, I have to mark each individual var/method public, which leads to a lot of boilerplate/noise and makes everything harder to read:

  public struct MyStruct {
    public var a:Int
    public var b:Int
    private var c:Int
    public var d:Int
  }

Notice that the private var doesn’t really stand out as such very well. Also, it is exceedingly rare (at least in my own coding style) that I actually want an internal variable unless the type itself is internal, and in those cases, I would like that choice to stand out as deliberate the same way I want ‘private' to stand out. As it stands, I wait until I think I am done modifying a type to mark it public because of the extra noise generated. I also make a point to write ‘internal' for things that I explicitly want to restrict to internal.

Consider the alternative:

  public struct MyStruct {
    var a:Int
    var b:Int
    private var c:Int
    var d:Int
  }

Now the fact that I have chosen to make ‘c’ private really stands out much better. When revisiting the code in 6 months, the struct is much more “glance-able” (as a friend of mine likes to say).

Note also the nuance that I didn’t say that those vars were marked public (or had the same modifier), I said that they had the SAME VISIBILITY as the enclosing scope (which in this case happens to be public). This is a concept which is hard to express currently, and IIRC this is what we had to do to make the edge cases of swift 3’s private modifier work properly.

Basically, it already works this way for ‘private’, ‘fileprivate’, & ‘internal’, just not for ‘public’ or ‘open’… which can be surprising, especially since you don’t discover these differences until you are working across modules. We should just extend that mental model up to include public and open. Migration would just take internal variables of public/open types and mark them explicitly with the word ‘internal'.

Thanks,
Jon


(Matthew Johnson) #2

I would like to propose a change to the default access modifier within an enclosing scope. The default for top level definitions would stay internal, but anything within a scope would by default have the same visibility as it’s enclosing scope.

The main reason for this is readability/maintainability, and having the intention clearly stand out. It would also reduce a great amount of boilerplate. It also matches the mental model of how scopes normally work regarding inheritance of visibility/properties (which means less to teach newbies).

Right now if I want to make a type and all of it’s vars/methods public, I have to mark each individual var/method public, which leads to a lot of boilerplate/noise and makes everything harder to read:

   public struct MyStruct {
       public var a:Int
       public var b:Int
       private var c:Int
       public var d:Int
   }

Notice that the private var doesn’t really stand out as such very well. Also, it is exceedingly rare (at least in my own coding style) that I actually want an internal variable unless the type itself is internal, and in those cases, I would like that choice to stand out as deliberate the same way I want ‘private' to stand out. As it stands, I wait until I think I am done modifying a type to mark it public because of the extra noise generated. I also make a point to write ‘internal' for things that I explicitly want to restrict to internal.

Consider the alternative:

   public struct MyStruct {
       var a:Int
       var b:Int
       private var c:Int
       var d:Int
   }

There was a very intentional decision made to *not* do this. The reason is that we don't want to allow a library to accidentally commit to a public API contract through an "error of omission". For example, suppose `d` wasn't really intended to be visible outside the module but the author forgot to add an `internal` annotation. This is a problem as soon as the library is published.

I think this was the right decision. It's also worth noting the fact that most Swift users have a couple years of experience where omitting the access modifier means `internal`. This means an "error of omission" in this regard is more likely for experienced Swift users than it might otherwise be if things worked the way you suggest from the beginning.

···

Sent from my iPad

On Feb 13, 2017, at 3:02 AM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

Now the fact that I have chosen to make ‘c’ private really stands out much better. When revisiting the code in 6 months, the struct is much more “glance-able” (as a friend of mine likes to say).

Note also the nuance that I didn’t say that those vars were marked public (or had the same modifier), I said that they had the SAME VISIBILITY as the enclosing scope (which in this case happens to be public). This is a concept which is hard to express currently, and IIRC this is what we had to do to make the edge cases of swift 3’s private modifier work properly.

Basically, it already works this way for ‘private’, ‘fileprivate’, & ‘internal’, just not for ‘public’ or ‘open’… which can be surprising, especially since you don’t discover these differences until you are working across modules. We should just extend that mental model up to include public and open. Migration would just take internal variables of public/open types and mark them explicitly with the word ‘internal'.

Thanks,
Jon

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(David Goodine) #3

I think having a different default access for nested-scope adds a significant amount of cognitive complexity for not much value. I understand that structs as nested types are often used to communicate values outside the score (internal even), so public will be required often in these cases. But, then the question is what about structs that are purely for internal use, which happens just as often? Or classes?

Furthermore, you can achieve the same level of clarity without source-breaking changes to the language simply by better code organization, as in:

  public struct MyStruct {
    public var a:Int
    public var b:Int
    public var d:Int

    private var c:Int
  }

-d

···

On Feb 13, 2017, at 4:02 AM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

I would like to propose a change to the default access modifier within an enclosing scope. The default for top level definitions would stay internal, but anything within a scope would by default have the same visibility as it’s enclosing scope.

The main reason for this is readability/maintainability, and having the intention clearly stand out. It would also reduce a great amount of boilerplate. It also matches the mental model of how scopes normally work regarding inheritance of visibility/properties (which means less to teach newbies).

Right now if I want to make a type and all of it’s vars/methods public, I have to mark each individual var/method public, which leads to a lot of boilerplate/noise and makes everything harder to read:

  public struct MyStruct {
    public var a:Int
    public var b:Int
    private var c:Int
    public var d:Int
  }

Notice that the private var doesn’t really stand out as such very well. Also, it is exceedingly rare (at least in my own coding style) that I actually want an internal variable unless the type itself is internal, and in those cases, I would like that choice to stand out as deliberate the same way I want ‘private' to stand out. As it stands, I wait until I think I am done modifying a type to mark it public because of the extra noise generated. I also make a point to write ‘internal' for things that I explicitly want to restrict to internal.

Consider the alternative:

  public struct MyStruct {
    var a:Int
    var b:Int
    private var c:Int
    var d:Int
  }

Now the fact that I have chosen to make ‘c’ private really stands out much better. When revisiting the code in 6 months, the struct is much more “glance-able” (as a friend of mine likes to say).

Note also the nuance that I didn’t say that those vars were marked public (or had the same modifier), I said that they had the SAME VISIBILITY as the enclosing scope (which in this case happens to be public). This is a concept which is hard to express currently, and IIRC this is what we had to do to make the edge cases of swift 3’s private modifier work properly.

Basically, it already works this way for ‘private’, ‘fileprivate’, & ‘internal’, just not for ‘public’ or ‘open’… which can be surprising, especially since you don’t discover these differences until you are working across modules. We should just extend that mental model up to include public and open. Migration would just take internal variables of public/open types and mark them explicitly with the word ‘internal'.

Thanks,
Jon

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Rien) #4

+1

I think this is actually what most people would intuitively expect anyway.

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: http://github.com/Balancingrock
Project: http://swiftfire.nl

···

On 13 Feb 2017, at 10:02, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

I would like to propose a change to the default access modifier within an enclosing scope. The default for top level definitions would stay internal, but anything within a scope would by default have the same visibility as it’s enclosing scope.

The main reason for this is readability/maintainability, and having the intention clearly stand out. It would also reduce a great amount of boilerplate. It also matches the mental model of how scopes normally work regarding inheritance of visibility/properties (which means less to teach newbies).

Right now if I want to make a type and all of it’s vars/methods public, I have to mark each individual var/method public, which leads to a lot of boilerplate/noise and makes everything harder to read:

  public struct MyStruct {
    public var a:Int
    public var b:Int
    private var c:Int
    public var d:Int
  }

Notice that the private var doesn’t really stand out as such very well. Also, it is exceedingly rare (at least in my own coding style) that I actually want an internal variable unless the type itself is internal, and in those cases, I would like that choice to stand out as deliberate the same way I want ‘private' to stand out. As it stands, I wait until I think I am done modifying a type to mark it public because of the extra noise generated. I also make a point to write ‘internal' for things that I explicitly want to restrict to internal.

Consider the alternative:

  public struct MyStruct {
    var a:Int
    var b:Int
    private var c:Int
    var d:Int
  }

Now the fact that I have chosen to make ‘c’ private really stands out much better. When revisiting the code in 6 months, the struct is much more “glance-able” (as a friend of mine likes to say).

Note also the nuance that I didn’t say that those vars were marked public (or had the same modifier), I said that they had the SAME VISIBILITY as the enclosing scope (which in this case happens to be public). This is a concept which is hard to express currently, and IIRC this is what we had to do to make the edge cases of swift 3’s private modifier work properly.

Basically, it already works this way for ‘private’, ‘fileprivate’, & ‘internal’, just not for ‘public’ or ‘open’… which can be surprising, especially since you don’t discover these differences until you are working across modules. We should just extend that mental model up to include public and open. Migration would just take internal variables of public/open types and mark them explicitly with the word ‘internal'.

Thanks,
Jon

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Adrian Zubarev) #5

–1 for me.

IMO the current behavior reduces all that internal noise in large projects, where the author only makes a small part of the API public. Furthermore this will break the implicit initializer on structs and make it implicitly public. Leaving the initializer as internal while everything else is public as a workaround would be inconsistent solution, so that’s a no go. Personally I think that will introduce more noise than the current behavior, because we’ll have to repeat internal all over the place in large projects. So in my eyes this would be a regression.

P.S.: I’m curious what the core team has to say about this.

···

--
Adrian Zubarev
Sent with Airmail

Am 13. Februar 2017 um 10:02:39, Jonathan Hull via swift-evolution (swift-evolution@swift.org) schrieb:

I would like to propose a change to the default access modifier within an enclosing scope. The default for top level definitions would stay internal, but anything within a scope would by default have the same visibility as it’s enclosing scope.

The main reason for this is readability/maintainability, and having the intention clearly stand out. It would also reduce a great amount of boilerplate. It also matches the mental model of how scopes normally work regarding inheritance of visibility/properties (which means less to teach newbies).

Right now if I want to make a type and all of it’s vars/methods public, I have to mark each individual var/method public, which leads to a lot of boilerplate/noise and makes everything harder to read:

public struct MyStruct {
public var a:Int
public var b:Int
private var c:Int
public var d:Int
}

Notice that the private var doesn’t really stand out as such very well. Also, it is exceedingly rare (at least in my own coding style) that I actually want an internal variable unless the type itself is internal, and in those cases, I would like that choice to stand out as deliberate the same way I want ‘private' to stand out. As it stands, I wait until I think I am done modifying a type to mark it public because of the extra noise generated. I also make a point to write ‘internal' for things that I explicitly want to restrict to internal.

Consider the alternative:

public struct MyStruct {
var a:Int
var b:Int
private var c:Int
var d:Int
}

Now the fact that I have chosen to make ‘c’ private really stands out much better. When revisiting the code in 6 months, the struct is much more “glance-able” (as a friend of mine likes to say).

Note also the nuance that I didn’t say that those vars were marked public (or had the same modifier), I said that they had the SAME VISIBILITY as the enclosing scope (which in this case happens to be public). This is a concept which is hard to express currently, and IIRC this is what we had to do to make the edge cases of swift 3’s private modifier work properly.

Basically, it already works this way for ‘private’, ‘fileprivate’, & ‘internal’, just not for ‘public’ or ‘open’… which can be surprising, especially since you don’t discover these differences until you are working across modules. We should just extend that mental model up to include public and open. Migration would just take internal variables of public/open types and mark them explicitly with the word ‘internal'.

Thanks,
Jon

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Xiaodi Wu) #6

It bears mentioning that it does _not_ formally work this way with
fileprivate or private types. The default access level for members is
always internal. This was a deliberate change I suggested for SE-0025 and
is new for Swift 3. It's just also the case that the rules were relaxed to
allow you to mock up greater access for members than the containing type
(for instance, you can label members public inside a fileprivate type).

The suggestion of defaulting members to the access of the enclosing scope
was considered and rejected for the reasons enumerated already by others:
namely, that public members should always be explicitly annotated. This
becomes doubly important now that we have open as a full-fledged access
level. There is also the little problem that private in the enclosing scope
is not an utterable level in the enclosed scope, which is neatly obviated
by the fact that the default access level is internal.

···

On Mon, Feb 13, 2017 at 03:02 Jonathan Hull via swift-evolution < swift-evolution@swift.org> wrote:

I would like to propose a change to the default access modifier within an
enclosing scope. The default for top level definitions would stay
internal, but anything within a scope would by default have the same
visibility as it’s enclosing scope.

The main reason for this is readability/maintainability, and having the
intention clearly stand out. It would also reduce a great amount of
boilerplate. It also matches the mental model of how scopes normally work
regarding inheritance of visibility/properties (which means less to teach
newbies).

Right now if I want to make a type and all of it’s vars/methods public, I
have to mark each individual var/method public, which leads to a lot of
boilerplate/noise and makes everything harder to read:

        public struct MyStruct {
                public var a:Int
                public var b:Int
                private var c:Int
                public var d:Int
        }

Notice that the private var doesn’t really stand out as such very well.
Also, it is exceedingly rare (at least in my own coding style) that I
actually want an internal variable unless the type itself is internal, and
in those cases, I would like that choice to stand out as deliberate the
same way I want ‘private' to stand out. As it stands, I wait until I think
I am done modifying a type to mark it public because of the extra noise
generated. I also make a point to write ‘internal' for things that I
explicitly want to restrict to internal.

Consider the alternative:

        public struct MyStruct {
                var a:Int
                var b:Int
                private var c:Int
                var d:Int
        }

Now the fact that I have chosen to make ‘c’ private really stands out much
better. When revisiting the code in 6 months, the struct is much more
“glance-able” (as a friend of mine likes to say).

Note also the nuance that I didn’t say that those vars were marked public
(or had the same modifier), I said that they had the SAME VISIBILITY as the
enclosing scope (which in this case happens to be public). This is a
concept which is hard to express currently, and IIRC this is what we had to
do to make the edge cases of swift 3’s private modifier work properly.

Basically, it already works this way for ‘private’, ‘fileprivate’, &
‘internal’, just not for ‘public’ or ‘open’… which can be surprising,
especially since you don’t discover these differences until you are working
across modules. We should just extend that mental model up to include
public and open. Migration would just take internal variables of
public/open types and mark them explicitly with the word ‘internal'.

Thanks,
Jon

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Charlie Monroe) #7

-1. Aside from everyone being used to current behavior that I personally do find more logical since it prevents you from accidently exposing internal members via public API, the migrator won't have a choice but to slap "internal" everywhere during migration and like there are now projects full of "fileprivate", there will be projects full of "internal".

···

On Feb 13, 2017, at 10:02 AM, Jonathan Hull via swift-evolution <swift-evolution@swift.org> wrote:

I would like to propose a change to the default access modifier within an enclosing scope. The default for top level definitions would stay internal, but anything within a scope would by default have the same visibility as it’s enclosing scope.

The main reason for this is readability/maintainability, and having the intention clearly stand out. It would also reduce a great amount of boilerplate. It also matches the mental model of how scopes normally work regarding inheritance of visibility/properties (which means less to teach newbies).

Right now if I want to make a type and all of it’s vars/methods public, I have to mark each individual var/method public, which leads to a lot of boilerplate/noise and makes everything harder to read:

  public struct MyStruct {
    public var a:Int
    public var b:Int
    private var c:Int
    public var d:Int
  }

Notice that the private var doesn’t really stand out as such very well. Also, it is exceedingly rare (at least in my own coding style) that I actually want an internal variable unless the type itself is internal, and in those cases, I would like that choice to stand out as deliberate the same way I want ‘private' to stand out. As it stands, I wait until I think I am done modifying a type to mark it public because of the extra noise generated. I also make a point to write ‘internal' for things that I explicitly want to restrict to internal.

Consider the alternative:

  public struct MyStruct {
    var a:Int
    var b:Int
    private var c:Int
    var d:Int
  }

Now the fact that I have chosen to make ‘c’ private really stands out much better. When revisiting the code in 6 months, the struct is much more “glance-able” (as a friend of mine likes to say).

Note also the nuance that I didn’t say that those vars were marked public (or had the same modifier), I said that they had the SAME VISIBILITY as the enclosing scope (which in this case happens to be public). This is a concept which is hard to express currently, and IIRC this is what we had to do to make the edge cases of swift 3’s private modifier work properly.

Basically, it already works this way for ‘private’, ‘fileprivate’, & ‘internal’, just not for ‘public’ or ‘open’… which can be surprising, especially since you don’t discover these differences until you are working across modules. We should just extend that mental model up to include public and open. Migration would just take internal variables of public/open types and mark them explicitly with the word ‘internal'.

Thanks,
Jon

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Karl) #8

I actually think “internal” is something which is worth calling out explicitly. It says that something is visible to other types in the project but not generally exported as part of the library’s API, which isn’t necessarily obvious. Implicit initialisers can be defined as having the same visibility as the type which they initialise.

Would be a huge source-breaking change though. I’m not sure anybody’s really 100% happy with our access modifiers, but it’s such a big change the core-team would understandably be reluctant to do it.

- Karl

···

On 13 Feb 2017, at 14:24, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

–1 for me.

IMO the current behavior reduces all that internal noise in large projects, where the author only makes a small part of the API public. Furthermore this will break the implicit initializer on structs and make it implicitly public. Leaving the initializer as internal while everything else is public as a workaround would be inconsistent solution, so that’s a no go. Personally I think that will introduce more noise than the current behavior, because we’ll have to repeat internal all over the place in large projects. So in my eyes this would be a regression.

P.S.: I’m curious what the core team has to say about this.


(Xiaodi Wu) #9

The purpose of an implicit internal, at least a big one, is for progressive
disclosure. It allows learners to write useful types (and indeed entire
apps) before they learn about access levels. Of the existing access levels
only internal fits the bill.

I do agree that for optimal style internal should be written out; indeed I
write out the access level for each member. I really don't see a need to
force everyone to adopt that style though, as long as we have one
consistent rule for what the default is.

···

On Mon, Feb 13, 2017 at 07:38 Karl Wagner via swift-evolution < swift-evolution@swift.org> wrote:

On 13 Feb 2017, at 14:24, Adrian Zubarev via swift-evolution < > swift-evolution@swift.org> wrote:

–1 for me.

IMO the current behavior reduces all that internal noise in large
projects, where the author only makes a small part of the API public.
Furthermore this will break the implicit initializer on structs and make it
implicitly public. Leaving the initializer as internal while everything
else is public as a workaround would be inconsistent solution, so that’s
a no go. Personally I think that will introduce more noise than the current
behavior, because we’ll have to repeat internal all over the place in
large projects. So in my eyes this would be a regression.

P.S.: I’m curious what the core team has to say about this.

I actually think “internal” is something which is worth calling out
explicitly. It says that something is visible to other types in the project
but not generally exported as part of the library’s API, which isn’t
necessarily obvious. Implicit initialisers can be defined as having the
same visibility as the type which they initialise.

Would be a huge source-breaking change though. I’m not sure anybody’s
really 100% happy with our access modifiers, but it’s such a big change the
core-team would understandably be reluctant to do it.

- Karl

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution