titzer / virgil

A fast and lightweight native programming language
1.2k stars 42 forks source link

Weakening `private` to file scope #136

Closed titzer closed 1 year ago

titzer commented 1 year ago

I am thinking of making a significant change to the meaning of the private keyword. Currently, private indicates that a field or method is only visible within the scope of the class or component or variant that it was declared in. Subclasses, for example, would not have access.

However, I recently added a new meaning for this keyword where private makes a type local to the namespace of a file (rather than the program).

That just gave me an idea: if the meaning of private always meant that the field, method, or type is only visible in the file, including subclasses in this file, other classes in this file, and even top-level methods and initializers in this file, then several problems are solved at once:

  1. Subclasses can access private state of superclasses, while outside classes cannot, just like Java's protected.
  2. Utility methods declared inside the file that accompany the definition (which in Java are often static methods of the class), can access private state, e.g. to more efficiently manipulate objects using internal representation knowledge.
  3. A constructor could be marked as private, making it possible to only construct objects through utility methods that are explicitly exposed outside the file.

Example for utility methods:

class Vector<T> {
    private var array: Array<T>;
    private def var length: int;

   ...
}

// public utility methods to construct vectors.
component Vectors {
    def concat<T>(x: Vector<T>, y: Vector<T>) -> Vector<T> {
       ... internal direct uses of fields {x.array} and {y.length} ...
    }
}

Example for protected emulation.

file: MyClass.v3
-------------------
class MyClass {
  private def internalMethod();
}
class MySubClass extends MyClass {
  def publicMethod() {
    internalMethod(); // visible here because in same file
  }
}

file: Other.v3
------------------
var x = MySubClass.new();
var d = x.internalMethod(); // not visible here, not even in a subclass defined here.

I really like this idea because it means:

  1. No friend classes for spooky-action at a distance: private shenanigans must be in same file!
  2. No code-safari hunting for all the uses of a private field/method/class: they are in this one file!
  3. Allows some OO patterns (like super method) without falling prey to fragile base class problems: all classes that have tighter contracts are in the same file!
  4. ONE MEANING: a private declaration is only visible in the file it is declared in.

Wdyt?

k-sareen commented 1 year ago

So like static in C/C++, but a bit nicer. Sounds good. One thought is if an import or module system is ever added, then it may be useful to have module-private functions/classes as well (something like Rust's pub(super)). But maybe that overcomplicates the design.

titzer commented 1 year ago

I've implemented this in a branch but haven't merged it yet because I am thinking about how it may slightly surprise some programmers.

E.g. a Java expert might be slightly surprised that private applies to the whole file (i.e. wider scope than assumed). However, I think this is alright because the Java tendency to split classes one-per-file means this isn't much different. I don't think it's too jarring if Virgil has a few more classes and related utility code per file, and it may even be cleaner for reasons above.

So I'll probably land this soon.

k-sareen commented 1 year ago

I've implemented this in a branch but haven't merged it yet because I am thinking about how it may slightly surprise some programmers.

Just need clear, concise documentation, I would say. static in C != static in Java for example and Java experts who came from C have managed.

titzer commented 1 year ago

Completed in https://github.com/titzer/virgil/commit/5a38d1f6b97a0575a90a720619ca5fe2bab883fb.