Simplifying Access Using 'Hidden'


(Jon Hull) #1

I wanted to propose a second simplification to the access system which would have several benefits:

  • It would remove ‘fileprivate’
  • It would allow extensions to be in their own files
  • It would serve the needs of ‘protected’ without the complications involved in that
  • It would serve some of the needs of submodules (but also work together with them really nicely when we have both)
  • It would allow protocols to have something similar to private methods, which are not exposed to callers of the protocol, but still required for conformance

My proposal is to add a ‘hidden’ access modifier which would act as a separate AXIS as opposed to a new level. Thus, it could be combined with any of the other modifiers (e.g. 'public hidden var’). The ‘fileprivate’ level would be removed.

Anything which is marked hidden is not visible outside of the file it is defined in. That visibility can be explicitly restored on a per file basis (but only within the defined access level). Take a look at the following:

  //File: MyStruct.swift

  struct MyStruct {
    var a:Int
    hidden var b:Int
  }

  extension MyStruct {
    var biggest:Int {return max(a,b)} //We can still see ‘b’ because it is only hidden outside the file
  }

Here we see that ‘b’ behaves very similarly to the way fileprivate works now. ‘b’ is technically still internal, but it is hidden and thus can’t be seen outside the file. The difference is that instead of being forced to add all of our extensions in the same file, we can organize them however we prefer.

  //In another file
  import hidden MyStruct //This exposes all ‘hidden’ items from the file MyStruct to this file

  extension MyStruct {
    var c:Int {return a + b} //We can see ‘b’ because of the ‘import hidden’ statement
  }

Notice how the intent has been shown here. ‘b’ is marked ‘internal’, which means we know that no one can see ‘b’ outside of the module. In addition ‘b’ is marked ‘hidden’, which means that the author is saying that this should only really be accessed from extensions, subclasses, or types which would be called friends in other languages. The actual guarantee is still ‘internal’, but a caller does have to explicitly request the access by typing ‘import hidden’, which will stop accidental/casual misuse. (If/when we get submodules, then we can make tighter guarantees)

This also allows a class to “hide the ejection seat levers” from its callers, while still allowing access for subclasses and extensions (i.e. it does most of what ‘protected’ would do, but with swift’s simpler file-based access model)

The same is true of protocols. There are often methods I have to include on protocols which are needed by the default implementations, but NEVER meant to be called directly by callers of the protocol. If vars/methods of a protocol are marked hidden, then they would be hidden from callers of the protocol outside the file. They are still required for conformance, however*.

  protocol MyProtocol {
    var a:Int
    hidden var b:Int //Conformers still need to provide this, but callers can’t see it
  }

  extension MyProtocol {
    func doSomethingBasedOnB() {
      //The extension can see b in the same file, but callers in other files won’t have access to ‘b’ directly
    }
  }

I think all of this works really well with Swift’s goal of progressive disclosure. Users of protocols/classes/etc… are not exposed to the internals necessary for extension (e.g. it won’t come up in autocomplete) until they actually need to conform/subclass/extend. Users won’t even need to learn about ‘hidden' until they are trying to extend a type which uses it (in a way which requires access to those internals), or they are writing their own framework. It has a simple and consistent meaning based on Swift’s original file-based access, but allows power/flexibility (without too much bookkeeping/boilerplate) where needed.

Thanks,
Jon

* One detail needed to make things useful for protocols, is that both hidden and non-hidden vars/methods on the conforming type should count towards conformance of hidden vars/methods on the protocol. Basically, marking something as ‘hidden’ on a protocol means it isn’t seen by the caller (only conformers/extensions). It makes sense to allow the conformer not to expose that either, unless desired. It would technically work fine without this allowance, but it ‘feels right’ and people would ask for it quickly...


(Adrian Zubarev) #2

–1

I won’t even try to be constructive on this one. It simply makes me tired of all this access modifier mess. open, closed, public, internal, now hidden, fileprivate, directoryprivate, moduleprivate, private, I might even forget some of the proposed access modifiers.

Instead of adding new stuff that explodes the complexity we should put our energy and fix existing issues, like the inconsistent open for example.

···

--
Adrian Zubarev
Sent with Airmail

Am 13. Februar 2017 um 15:06:45, Jonathan Hull via swift-evolution (swift-evolution@swift.org) schrieb:

I wanted to propose a second simplification to the access system which would have several benefits:

• It would remove ‘fileprivate’
• It would allow extensions to be in their own files
• It would serve the needs of ‘protected’ without the complications involved in that
• It would serve some of the needs of submodules (but also work together with them really nicely when we have both)
• It would allow protocols to have something similar to private methods, which are not exposed to callers of the protocol, but still required for conformance

My proposal is to add a ‘hidden’ access modifier which would act as a separate AXIS as opposed to a new level. Thus, it could be combined with any of the other modifiers (e.g. 'public hidden var’). The ‘fileprivate’ level would be removed.

Anything which is marked hidden is not visible outside of the file it is defined in. That visibility can be explicitly restored on a per file basis (but only within the defined access level). Take a look at the following:

//File: MyStruct.swift

struct MyStruct {
var a:Int
hidden var b:Int
}

extension MyStruct {
var biggest:Int {return max(a,b)} //We can still see ‘b’ because it is only hidden outside the file
}

Here we see that ‘b’ behaves very similarly to the way fileprivate works now. ‘b’ is technically still internal, but it is hidden and thus can’t be seen outside the file. The difference is that instead of being forced to add all of our extensions in the same file, we can organize them however we prefer.

//In another file
import hidden MyStruct //This exposes all ‘hidden’ items from the file MyStruct to this file

extension MyStruct {
var c:Int {return a + b} //We can see ‘b’ because of the ‘import hidden’ statement
}

Notice how the intent has been shown here. ‘b’ is marked ‘internal’, which means we know that no one can see ‘b’ outside of the module. In addition ‘b’ is marked ‘hidden’, which means that the author is saying that this should only really be accessed from extensions, subclasses, or types which would be called friends in other languages. The actual guarantee is still ‘internal’, but a caller does have to explicitly request the access by typing ‘import hidden’, which will stop accidental/casual misuse. (If/when we get submodules, then we can make tighter guarantees)

This also allows a class to “hide the ejection seat levers” from its callers, while still allowing access for subclasses and extensions (i.e. it does most of what ‘protected’ would do, but with swift’s simpler file-based access model)

The same is true of protocols. There are often methods I have to include on protocols which are needed by the default implementations, but NEVER meant to be called directly by callers of the protocol. If vars/methods of a protocol are marked hidden, then they would be hidden from callers of the protocol outside the file. They are still required for conformance, however*.

protocol MyProtocol {
var a:Int
hidden var b:Int //Conformers still need to provide this, but callers can’t see it
}

extension MyProtocol {
func doSomethingBasedOnB() {
//The extension can see b in the same file, but callers in other files won’t have access to ‘b’ directly
}
}

I think all of this works really well with Swift’s goal of progressive disclosure. Users of protocols/classes/etc… are not exposed to the internals necessary for extension (e.g. it won’t come up in autocomplete) until they actually need to conform/subclass/extend. Users won’t even need to learn about ‘hidden' until they are trying to extend a type which uses it (in a way which requires access to those internals), or they are writing their own framework. It has a simple and consistent meaning based on Swift’s original file-based access, but allows power/flexibility (without too much bookkeeping/boilerplate) where needed.

Thanks,
Jon

* One detail needed to make things useful for protocols, is that both hidden and non-hidden vars/methods on the conforming type should count towards conformance of hidden vars/methods on the protocol. Basically, marking something as ‘hidden’ on a protocol means it isn’t seen by the caller (only conformers/extensions). It makes sense to allow the conformer not to expose that either, unless desired. It would technically work fine without this allowance, but it ‘feels right’ and people would ask for it quickly...

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


(Xiaodi Wu) #3

Hasn't "hidden" already been discussed? I could swear that it was already
pitched and feedback given.

In any case there's an ongoing discussion in another thread about removing
new private; it seems distinctly poor form to abandon that and propose
doing the exact opposite (removing old private) in a new thread.

···

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

I wanted to propose a second simplification to the access system which
would have several benefits:

        • It would remove ‘fileprivate’
        • It would allow extensions to be in their own files
        • It would serve the needs of ‘protected’ without the
complications involved in that
        • It would serve some of the needs of submodules (but also work
together with them really nicely when we have both)
        • It would allow protocols to have something similar to private
methods, which are not exposed to callers of the protocol, but still
required for conformance

My proposal is to add a ‘hidden’ access modifier which would act as a
separate AXIS as opposed to a new level. Thus, it could be combined with
any of the other modifiers (e.g. 'public hidden var’). The ‘fileprivate’
level would be removed.

Anything which is marked hidden is not visible outside of the file it is
defined in. That visibility can be explicitly restored on a per file basis
(but only within the defined access level). Take a look at the following:

        //File: MyStruct.swift

        struct MyStruct {
                var a:Int
                hidden var b:Int
        }

        extension MyStruct {
                var biggest:Int {return max(a,b)} //We can still see ‘b’
because it is only hidden outside the file
        }

Here we see that ‘b’ behaves very similarly to the way fileprivate works
now. ‘b’ is technically still internal, but it is hidden and thus can’t be
seen outside the file. The difference is that instead of being forced to
add all of our extensions in the same file, we can organize them however we
prefer.

        //In another file
        import hidden MyStruct //This exposes all ‘hidden’ items from the
file MyStruct to this file

        extension MyStruct {
                var c:Int {return a + b} //We can see ‘b’ because of the
‘import hidden’ statement
        }

Notice how the intent has been shown here. ‘b’ is marked ‘internal’,
which means we know that no one can see ‘b’ outside of the module. In
addition ‘b’ is marked ‘hidden’, which means that the author is saying that
this should only really be accessed from extensions, subclasses, or types
which would be called friends in other languages. The actual guarantee is
still ‘internal’, but a caller does have to explicitly request the access
by typing ‘import hidden’, which will stop accidental/casual misuse.
(If/when we get submodules, then we can make tighter guarantees)

This also allows a class to “hide the ejection seat levers” from its
callers, while still allowing access for subclasses and extensions (i.e. it
does most of what ‘protected’ would do, but with swift’s simpler file-based
access model)

The same is true of protocols. There are often methods I have to include
on protocols which are needed by the default implementations, but NEVER
meant to be called directly by callers of the protocol. If vars/methods of
a protocol are marked hidden, then they would be hidden from callers of the
protocol outside the file. They are still required for conformance,
however*.

        protocol MyProtocol {
                var a:Int
                hidden var b:Int //Conformers still need to provide this,
but callers can’t see it
        }

        extension MyProtocol {
                func doSomethingBasedOnB() {
                        //The extension can see b in the same file, but
callers in other files won’t have access to ‘b’ directly
                }
        }

I think all of this works really well with Swift’s goal of progressive
disclosure. Users of protocols/classes/etc… are not exposed to the
internals necessary for extension (e.g. it won’t come up in autocomplete)
until they actually need to conform/subclass/extend. Users won’t even need
to learn about ‘hidden' until they are trying to extend a type which uses
it (in a way which requires access to those internals), or they are writing
their own framework. It has a simple and consistent meaning based on
Swift’s original file-based access, but allows power/flexibility (without
too much bookkeeping/boilerplate) where needed.

Thanks,
Jon

* One detail needed to make things useful for protocols, is that both
hidden and non-hidden vars/methods on the conforming type should count
towards conformance of hidden vars/methods on the protocol. Basically,
marking something as ‘hidden’ on a protocol means it isn’t seen by the
caller (only conformers/extensions). It makes sense to allow the conformer
not to expose that either, unless desired. It would technically work fine
without this allowance, but it ‘feels right’ and people would ask for it
quickly...

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