Analysis of existing scopes


(Joanna Carter) #1

After much to-ing and fro-ing about scopes on Swift, I, for one, am getting confused about exactly what effects the present scopes have on the various fundamental types used in Swift (protocols, classes, structs, enums, ordinal types)

Therefore, if you will indulge me, I would like to start a thread in which I will I will outline what is and what is not possible at present.

Let me start with protocols :

If I start with the lowest visibility - private

private protocol PrivateProtocol { }

private protocol DoublePrivateProtocol : PrivateProtocol
{
  var p: PrivateProtocol { get }
}

It would seem that, within the same file, private has the same meaning as fileprivate. Understandable to some degree but could be confusing to some.

We can both inherit from and use a private protocol as part of another private or fileprivate protocol

internal class Fred
{
  init() { }
  
  private var pp: PrivateProtocol
}

We can also implement or use a private protocol as part of another non-private, non-protocol type

internal protocol InternalProtocol : PrivateProtocol // error : Internal protocol cannot refine a fileprivate protocol
{
  var p: PrivateProtocol { get } // error : Property cannot be declared internal because its type uses a fileprivate type
}

However, we cannot either inherit from or use a private protocol as part of a non-private protocol. Totally understandable.

Both internal and public protocol scopes are also self explanatory, and it is obvious that, just as an internal protocol cannot refine or use a private protocol, neither can a public protocol cannot refine or use an internal one.

For this post, my only question is - why is private allowed when, in reality, it is fileprivate for protocols?

More to follow…

···

--
Joanna Carter
Carter Consulting


(Jon Hull) #2

For this post, my only question is - why is private allowed when, in reality, it is fileprivate for protocols?

Swift 3 private means private to the current scope. For top-level items, that scope is the file.

If we are allowed to nest protocols inside structs/classes (which I believe is happening in Swift 3.2), then we will be able to have protocols private to those types.

Thanks,
Jon


(Joanna Carter) #3

Now to classes :

Classes declared as private are actually just as visible as if they were declared to be fileprivate.

private class PrivateClass { }

internal class InternalClass
{
  private var pc: PrivateClass?
}

Understandable but, why have two visibilities that mean the same thing? Why can we declare a protocol or class as private? Convenience? Less typing?

Of course, we can extend a private class, as long as we declare the extension at file level :

private class PrivateClass { }

extension PrivateClass { }

However, if we try to nest a private class inside another (file)private type :

internal class InternalClass
{
  private class InternalPrivateClass { }
  
  private var ipc: InternalPrivateClass?
  
  extension InternalPrivateClass { } // error : Declaration is only valid at file scope
}

extension InternalClass.InternalPrivateClass { } // error : 'InternalPrivateClass' is inaccessible due to 'private' protection level

… we now find that we have to differentiate between private and fileprivate in order to extend the nested type :

internal class InternalClass
{
  fileprivate class InternalPrivateClass { }
  
  private var ipc: InternalPrivateClass?
}

extension InternalClass.InternalPrivateClass
{
  
}

A private nested class can be truly private but, as soon as you want to extend that class in the same scope, we now have to switch to fileprivate because it is not possible to extend the nested class anywhere other than at file level. So, I have now had to raise the visibility of my private nested class just to allow me to extend it.

Once again, this is not the end of the world, just inconsistent.

···

Le 22 févr. 2017 à 16:31, Joanna Carter <joanna@carterconsulting.org.uk> a écrit :

After much to-ing and fro-ing about scopes on Swift…

--
Joanna Carter
Carter Consulting


(Joanna Carter) #4

And now I find, what I consider to be, a very peculiar anomaly in method visibility.

private class PrivateBaseClass
{
  internal func internalFunc() { }
  
  public func publicFunc() { }
  
  open func openFunc() { }
}

private class PrivateDerivedClass : PrivateBaseClass
{
  fileprivate override func internalFunc() { }
  
  fileprivate override func publicFunc() { }
  
  fileprivate override func openFunc() { }
}

internal class InternalDerivedFromPrivateClass : PrivateBaseClass { } // error : Class cannot be declared internal because its superclass is fileprivate

Can somebody please explain why I can declare internal, public and open members on a private class, and even override them in a derived private class, all without even a compiler warning, when, as soon as I try to extend the visibility of a private class, I am told that I cannot so do.

I mean, it is blatantly obvious why you would not want higher visibility members in a limited visibility class, so why doesn't the compiler simply state that all members cannot have a visibility higher than the declaring class?

···

--
Joanna Carter
Carter Consulting


(Joanna Carter) #5

Indeed, that appears to be confusing but, as you more than likely found out, if you mark both the extension and its func as fileprivate, the problem goes away.

Nonetheless, with :

private extension Blah
{
  func test()
  {
    
  }
}

… the test() method is not visible outside of the file because the extension is private and, it would seem, that is enough to hide all of its code.

···

Le 22 févr. 2017 à 17:53, BJ Homer <bjhomer@gmail.com> a écrit :

My understanding is that “private” when written at file scope is exactly equivalent to “fileprivate”. That is, at the top level it’s tied to the file, not to the type’s scope. This means that the default access level within “private extension Foo {}” is actually *fileprivate*. This conflation of private and fileprivate is confusing.

Example: This compiles:

---------------

class Blah {
    
    func start() {
        self.test()
    }
}

private extension Blah {
    func test() {
        
    }
}

---------------

While this does not:

---------------

class Blah {
    
    func start() {
        self.test()
    }
}

private extension Blah {
    private func test() { // Note the “private” here
        
    }
}

--
Joanna Carter
Carter Consulting


(Matthew Johnson) #6

And now I find, what I consider to be, a very peculiar anomaly in method visibility.

private class PrivateBaseClass
{
internal func internalFunc() { }

public func publicFunc() { }

open func openFunc() { }
}

private class PrivateDerivedClass : PrivateBaseClass
{
fileprivate override func internalFunc() { }

fileprivate override func publicFunc() { }

fileprivate override func openFunc() { }
}

internal class InternalDerivedFromPrivateClass : PrivateBaseClass { } // error : Class cannot be declared internal because its superclass is fileprivate

Can somebody please explain why I can declare internal, public and open members on a private class, and even override them in a derived private class, all without even a compiler warning, when, as soon as I try to extend the visibility of a private class, I am told that I cannot so do.

I mean, it is blatantly obvious why you would not want higher visibility members in a limited visibility class, so why doesn't the compiler simply state that all members cannot have a visibility higher than the declaring class?

The rationale for this is that it allows you to explore the eventual design of a type without exposing it. When you are confident in the design all you need to does modify the access modifier of the type itself. Without this capability people might resort to comments indicating the intended future visibility and then have to remember to go back and convert all of those comments into the new access modifier when you’re ready to publish the type.

The important thing to keep in mind is that software isn’t a fixed, static thing. It evolves over time. This is a tool to help grow software over time.

···

On Feb 23, 2017, at 4:47 AM, Joanna Carter via swift-evolution <swift-evolution@swift.org> wrote:

--
Joanna Carter
Carter Consulting

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


(Joanna Carter) #7

The rationale for this is that it allows you to explore the eventual design of a type without exposing it. When you are confident in the design all you need to does modify the access modifier of the type itself. Without this capability people might resort to comments indicating the intended future visibility and then have to remember to go back and convert all of those comments into the new access modifier when you’re ready to publish the type.

I suppose I can understand that but...

Certainly you can check that everything compiles but how does it help if you want to test the code does what you expect? What about testing the validity of inheritance controls?

Personally, I test new ideas in a "side" project before integrating into the main project. That say, I get to develop everything at the correct visibilities and with everything ready to just copy and paste to the main project.

The important thing to keep in mind is that software isn’t a fixed, static thing. It evolves over time. This is a tool to help grow software over time.

After over 25 years of developing software and teaching others, I have never heard of such a justification of such a mish-mash of visibilities. This might seem like a good idea for testing but, what about the effect of it on real development where inexperienced developers keep on finding that visibilities that are "possible" don't actually work?

···

Le 23 févr. 2017 à 14:59, Matthew Johnson <matthew@anandabits.com> a écrit :

--
Joanna Carter
Carter Consulting


(Matthew Johnson) #8

The rationale for this is that it allows you to explore the eventual design of a type without exposing it. When you are confident in the design all you need to does modify the access modifier of the type itself. Without this capability people might resort to comments indicating the intended future visibility and then have to remember to go back and convert all of those comments into the new access modifier when you’re ready to publish the type.

I suppose I can understand that but...

Certainly you can check that everything compiles but how does it help if you want to test the code does what you expect? What about testing the validity of inheritance controls?

Personally, I test new ideas in a "side" project before integrating into the main project. That say, I get to develop everything at the correct visibilities and with everything ready to just copy and paste to the main project.

Sometimes APIs are used internally in production while eventually planned for public visibility. New features in Apple’s frameworks often start life this way.

The important thing to keep in mind is that software isn’t a fixed, static thing. It evolves over time. This is a tool to help grow software over time.

After over 25 years of developing software and teaching others, I have never heard of such a justification of such a mish-mash of visibilities. This might seem like a good idea for testing but, what about the effect of it on real development where inexperienced developers keep on finding that visibilities that are "possible" don't actually work?

I don’t have a strong feeling about this particular capability. I’m just trying to state the rationale as I understand it.

···

On Feb 23, 2017, at 1:34 PM, Joanna Carter <joanna@carterconsulting.org.uk> wrote:

Le 23 févr. 2017 à 14:59, Matthew Johnson <matthew@anandabits.com> a écrit :

--
Joanna Carter
Carter Consulting


(Joanna Carter) #9

Sometimes APIs are used internally in production while eventually planned for public visibility. New features in Apple’s frameworks often start life this way.

Hmmm. In my experience, if I wanted to experiment with something in the context of the whole project, I would tend to create a branch of the project in source control, experiment and either merge the branch if successful or delete it if not.

I don’t have a strong feeling about this particular capability. I’m just trying to state the rationale as I understand it.

Pease don't think I am criticising your understanding, it's just that such a rationale doesn't seem very, well, rational :slight_smile:

···

Le 23 févr. 2017 à 20:48, Matthew Johnson <matthew@anandabits.com> a écrit :

--
Joanna Carter
Carter Consulting


(Xiaodi Wu) #10

> Le 23 févr. 2017 à 20:48, Matthew Johnson <matthew@anandabits.com> a
écrit :
>
> Sometimes APIs are used internally in production while eventually
planned for public visibility. New features in Apple’s frameworks often
start life this way.

Hmmm. In my experience, if I wanted to experiment with something in the
context of the whole project, I would tend to create a branch of the
project in source control, experiment and either merge the branch if
successful or delete it if not.

> I don’t have a strong feeling about this particular capability. I’m
just trying to state the rationale as I understand it.

Pease don't think I am criticising your understanding, it's just that such
a rationale doesn't seem very, well, rational :slight_smile:

It is a useful generalization of an absolutely obligatory feature for new
`private`. Consider the following:

private class Foo {
  private class Bar {
    /* blank */ class Baz { }
  }
}

What access modifier can be used in place of `blank` to make `Foo.Bar.Baz`
have the same visibility as `Foo.Bar`? Note that `private` inside the type
is more restrictive than `private` in the declaration of the type; the
precise visibility of `Foo.Bar` is not expressible inside `Foo.Bar` itself.
To allow `Foo.Bar.Baz` to be visible everywhere that `Foo.Bar` is visible,
we must allow `blank` to be replaced by a modifier other than `private`.

···

On Fri, Feb 24, 2017 at 3:32 AM, Joanna Carter via swift-evolution < swift-evolution@swift.org> wrote:

--
Joanna Carter
Carter Consulting

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


(Joanna Carter) #11

Aaaarrrgghhh!!! Of course!

You'd have thought, having created massive frameworks in C#, which allows the same thing, and which I used extensively, I would have remembered that.

It just goes to show that we can often be so close to a subject that we "can't see the wood for the trees" :slight_smile:

···

Le 25 févr. 2017 à 02:55, Xiaodi Wu <xiaodi.wu@gmail.com> a écrit :

It is a useful generalization of an absolutely obligatory feature for new `private`. Consider the following:

private class Foo {
  private class Bar {
    /* blank */ class Baz { }
  }
}

What access modifier can be used in place of `blank` to make `Foo.Bar.Baz` have the same visibility as `Foo.Bar`? Note that `private` inside the type is more restrictive than `private` in the declaration of the type; the precise visibility of `Foo.Bar` is not expressible inside `Foo.Bar` itself. To allow `Foo.Bar.Baz` to be visible everywhere that `Foo.Bar` is visible, we must allow `blank` to be replaced by a modifier other than `private`.

--
Joanna Carter
Carter Consulting