[Draft] A Consistent Foundation For Access Control: Scope-Bounded Capabilities


(Joanna Carter) #1

Pardon me for jumping in without a whole load of quoting of previous messages but :

1. My mailbox is getting crammed with messages that are over 90% quote, sometimes running to hundreds of lines

2. I can't find a convenient message to attach this reply to

Matthew, please don't take any of the following as a personal attack of any kind but I am getting very concerned that this whole proposition is getting way too complicated for most programmers to cope. I can see a lot of them reverting to using whatever the default is for a given situation, only changing it if the compiler complains… and to hell with any thought of well written and protected code :frowning:

I hope I have got this right ; you are looking at separating scopes and capabilities? This is something I mentioned in my early attempts to communicate the problem of conflation of concerns over class visibility/ inheritability.

In the light of your proposal, may I contribute, what I hope is, a much simpler specification?

Starting with classes :

Since OO began, we have had the keywords: public, protected and private to control visibility; I am still not sure why we need to meddle with this well tried and tested model, apart from the consideration of extensions, but more on that later.

In C#, the only other visibility introduced was 'internal', to limit visibility to a given module, and a hybrid 'internal protected' which extended visibility of protected stuff to the module.

C# used 'sealed' to control inheritance, whereas Swift uses 'final'.

Still looking only at classes, I understand that Swift introduced 'open' and changed the meaning of 'public' to restrict the inheritability of a class to within its declaring module. This seemed odd as we already had 'final' for inheritance control, that could be used in conjunction with 'public' visibility, now to be confused with 'public' meaning 'public' inside a module but 'public final' from outside. Not forgetting 'open', which means public inside a module and public outside, but in its name, gives no indication whether it is a visibility specifier or an access control specifier.

If Swift really needs to control inheritability, then, as we seem agreed, this needs a separation of inheritability specifiers from visibility specifiers.

So, in considering the perceived need to be able to allow a class to be visible outside of a module whilst restricting its inheritability from code outside of the module, we now have an (extraneous) keyword that conflates visibility and inheritance control.

I would still argue strongly for the reinstatement of 'public final' to declare a class that is visible anywhere but that cannot be inherited, and 'public' to mean what it says on the tin (both in terms of visibility and inheritability).

Thus, using the "old" definition of 'public', we already had the following (module based) controls on classes :

internal class InternalClass // visibility - limited to the module : inheritable within the module
{
  internal func doIt() { } // visibility - limited to the module : overridable within the module
}

internal class InternalClass // visibility - limited to the module : inheritable within the module
{
  internal final func doIt() { } // visibility - limited to the module : not overridable
}

public class PublicClass // visibility - anywhere : inheritable anywhere
{
  internal func doIt() { } // visibility - limited to the module : overridable within the module
}

public class PublicClass // visibility - anywhere : inheritable anywhere
{
  internal final func doIt() { } // visibility - limited to the module : not overridable
}

public class PublicClass // visibility - anywhere : inheritable anywhere
{
  public func doIt() { } // visibility - anywhere : overridable anywhere
}

public class PublicClass // visibility - anywhere : inheritable anywhere
{
  public final func doIt() { } // visibility - anywhere : not overridable
}

The above declarations use clearly defined, separate, visibility and inheritability specifiers. Could you please say if I have missed any possible combinations?

So far, for classes, we have three visibility specifiers (private, public and internal) and one, separate, inheritance control specifier.

Extensions and Inheritance

Is it possible to state that inheriting from a class and creating an extension for a class are but two sides of the same coin? If so, can we treat such extension of any type commonly, so that we can use the same specifiers for protocols, classes, structs, enums, etc.

The one specifier that is missing from classes is commonly known as 'protected'

The one specifier that is missing from other types is one that limits visibility, to extensions to that type.

I suggested the 'extensible' specifier (or similar spelling), as a means of doing the same job that protected does for base classes, when extending any other type. Or could we reuse the 'final' keyword with any type, to mark either the type or any of its members, that we do not want to allow to be extendable?

This would mean that we can define non-class types, safe in the knowledge that another developer cannot add capabilities to that type that might affect any expected behaviour. At the moment, it is possible to do something like this :

// module 1
public struct Person
{
  public func doIt()
  {
    print("Person doIt")
  }
}

// module 2
protocol Imposter
{
  func doIt()
}

extension Person : Imposter
{
  func doIt()
  {
    print("Imposter doIt")
  }
}

No errors, no warnings, perfectly legitimate code that subverts the original intent of the type.

Swift doesn't normally allow you to "hide" or replace a base method as other languages do, unless you declare an extension that does the hiding for you.

However, if we could specify :

public struct Person
{
  public final func doIt()
  {
    print("Person doIt")
  }
}

Then the compiler could flag an error, should we try to write an extension with the intent of subverting the original method.

Likewise, as with a class, it could be possible to mark the whole struct as final, thus forbidding any extensions to it at all.

The remaining(?) part in the jigsaw is to:

1. limit access to members of types, other than classes, only to extensions

2. provide a common keyword to combine that principle with "protected" access, as found in classes

Example :

public struct Person
{
  extensible var name: String? // only visible in extensions
}

public class Person
{
  extensible var name: String? // replaces protected - only visible in subclasses or extensions
}

Summary

Visibility specifiers - public, internal, extensible, private

Inheritance control specifier - final

Question - do we really need much more than that? All this talk of submodules and file-based, directory-linked, etc, is starting to some more and more like the stringly-typed (identifying by strings) stuff that we have tried so hard to get rid of.

Please re-evaluate the myriad of options in the proposal, to see if they really need to be there, or whether they could be reduced to a much simpler solution :slight_smile:

···

--
Joanna Carter
Carter Consulting