verona Visibility specifications C++

We've had some internal discussions in the past about visibility and have a rough straw-robot proposal for wider discussion:

In terms of semantics, the proposal was to limit ourselves to public and package, for various reasons:

  • In the absence of subclassing, protected visibility does not mean anything.
  • A package will always be compiled together. Anything that is broken by changes to internal implementation details can be refactored at the same time.
  • Supporting only two kinds of visibility makes it very easy to decide which to use.

Open questions:

Can interfaces contain package-local methods? If so, what are the restrictions on casting a concrete type to an interface that contains package-local methods (is the cast allowed only in the package that defines the class? Is it allowed only if the interface is, itself, package-local? Are there some more complex rules?). If not, can generic methods be sufficiently expressive within a package for this not to be a problem?

What is the syntax for describing private types / methods? Different case for the start (used by Go and, by convention, C#)? Underscore at the start of package-local (informally used by Python and some C styles)? Explicit package / public keywords (used by most Simula-like languages)?

Asked Oct 08 '21 14:10
avatar davidchisnall
davidchisnall

6 Answer:

I think visibility restrictions have two distinct goals. The first one is limiting the API guarantee, to avoid breaking changes. For this, a “package” visibility is good enough.

The other goal is protecting invariants and allowing local reasoning about code. For this, I would say we want a smaller visibility unit, typically a module (which in most cases is one file).

That gives us three levels, public, package, private.

1
Answered Nov 14 '20 at 18:50
avatar  of plietar
plietar

IIUC, a module is a class, so would private be visible to other classes inside?

module Mod {
  a : U32;
  class Foo {
    b : F64;
    foo() : U32 { return a; } // is this assumed to work?
  }
  bar() : F64 { return Foo::b; } // or that?
}

What would be the default visibility? I'd assume private is default, and then we append public, export or some other keyword to get them public in the right context.

I would prefer to tag each member individually, rather than use C++'s public / private keywords in the class declaration.

1
Answered Nov 16 '20 at 10:56
avatar  of rengolin
rengolin

The current approach is that a module is a class, and there isn't a module keyword, because a module is composed of all of the files in a directory. That is, the translation unit is a directory, not a file.

@rengolin : your example doesn't make any sense to me. You seem to be using instance variables as if they were globals?

I think the "local reasoning" argument is very interesting. Limiting visibility scope does indeed do this, at least to some extent (although not always: a private member can be exposed via some other public or package member, whether that's a method or a nested type). My current inclination is to say that the reasoning scope is also the module, that is, as @davidchisnall points out, it's a single translation unit, and so is "fully visible" to the person/tool doing the reasoning. For me, the argument that this simplifies the choice of visibility level carries a lot of weight.

But the most important issue is the one about interfaces with non-public members. This problem also happens in Pony and TypeScript, and it is serious: if a class with a non-public member fulfills an interface from a different module with non-public members, that other module exposes the non-public API to unexpected code. I think a non-public member can only be fulfilled by a non-public member with the same visibility scope as the interface non-public member.

And that is, in some sense, the best argument for very simple visibility (i.e. just public and package), because there is a simple rule: an interface with a non-public member is only fulfilled by a class/interface from the same package.

For syntax, Pony also uses a leading underscore to indicate private (Pony uses Smalltalk-ish privacy rules: private methods are package-scope, private fields are class scope - although in Smalltalk, private fields are instance scope). This makes visibility determinable at the call site, which may or may not be a good choice. I'm inclined towards a syntax that encourages non-public as the default, but at the same time I find it more natural when I'm writing code to annotate non-public members.

I propose that, for now, we use two visibility scopes (public and package) and we use the private keyword for non-public (i.e. package scope) members.

1
Answered Nov 16 '20 at 11:24
avatar  of sylvanc
sylvanc

@rengolin : your example doesn't make any sense to me. You seem to be using instance variables as if they were globals?

Yes. I forgot Verona won't have global variables, not even inside "modules", which is the correct abstraction, given that "modules are classes".

1
Answered Dec 16 '20 at 12:26
avatar  of rengolin
rengolin

It does; however, have static fields of classes (presumably including classes that are modules - at least for interop purposes as the abstraction used to expose C++ globals to Verona), which gives you very similar semantics. Because static fields are not bound to a cown, they can contain only things that you'd be able to store in an immutable object (cown references, immutable pointers, read ends of noticeboards).

1
Answered Dec 16 '20 at 12:58
avatar  of davidchisnall
davidchisnall

An can those static fields be accessed without full lexical context?

module A { // as in, a file in directory A
  static f : U32;
  class Foo {
    foo() : U32 {
      // which one is valid? both?
      return A::f;
      return f;
    }
}

If they don't need full lexical context, and modules are classes, then can I also do:

class A {
  static f;
  class B {
    foo() { return f; }
  }
}

Which is less meaningful to me. So, I'd favour full lexical context to static field access in all cases.

1
Answered Dec 16 '20 at 13:31
avatar  of rengolin
rengolin