Skip to content

Support paired-up private and public variables: "Private var, public final" #3962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
eernstg opened this issue Jul 5, 2024 · 3 comments
Open
Labels
small-feature A small feature which is relatively cheap to implement.

Comments

@eernstg
Copy link
Member

eernstg commented Jul 5, 2024

It can be useful to have a variable declaration that allows for mutation in a limited scope (as in "we can read and write this variable, but they can only read it!"). Here is a way to model that concept, such that the current library can read and write the variable (using the private name), but other libraries can only read it (using the corresponding public name):

int _x = 0;
int get x => _x;

This idiom can be used with several kinds of variables (top-level, static, instance), but not with local variables (they can't be accessed from other libraries anyway).

However, there is a connection to formal parameters (which are a kind of local variables). In this proposal we have a named initializing formal parameter of the form this._x in a constructor (which will initialize a private variable named _x), and we would automatically use the corresponding public name x at call sites. So we allow "them" to initialize the variable, but only "we" can access it after initialization. (A named parameter whose name is private is an error today, but with that proposal it would be allowed.)

The fact that we're using a private name and the corresponding public name is not an irritating limitation, it's a feature! This is because we can allow for the syntax to be concise, and developers who have ever encountered this feature will immediately know what's going on, as opposed to the situation where the two names are declared independently. With separate declarations using independently chosen names, developers will need to read both names (more verbosity) and then understand and remember the connection between those two names. So it seems useful to pair up names like _x and x, and to be able to set up this pairing concisely.

We can use a lexical rule to allow a _? to occur at the beginning of a new kind of identifiers, for example _?x. The idea is that (just like regular expressions) the ? hints that the _ may or may not be there. So _?x declares two names together, namely _x and x. The use of those names follows from the kind of declaration where this name occurs. For example:

int _?x = 0;
// Means
//   int _x = 0;
//   int get x => _x;

class A {
  late final int _?i;
  // late final int _i;
  // int get i => _i;

  final int _j;

  A({this._?j = 10});
  // A({int j = 10}): _j = j;
}

class const A({required int _?i});
// class A {
//   final int _i;
//   const A({required int i}): _i = i; 
// }

Here are the required grammar changes:

// Grammar rules.

<initializedIdentifier> ::= // Updated rule.
    <identifierOrPrivatePublic> ('=' <expression>)?

<fieldFormalParameter> ::= // Updated rule.
    <finalConstVarOrType>? 'this' '.' <identifierOrPrivatePublic> (<formalParameterPart> '?'?)?
 
<identifierOrPrivatePublic> ::= // Updated rule.
    <identifier> |
    <PRIVATE_PUBLIC_IDENTIFIER>

// Lexical rules.

<OTHER_IDENTIFIER> ::= // Lexical rule, used to be a grammar rule.
    'async' | 'augment' | 'base' | 'hide' | 'of' | 'on' | 'sealed' | 'show' | 'sync' | 'when' | 'type'

<PRIVATE_PUBLIC_IDENTIFIER> ::= // New rule.
    '_?' (IDENTIFIER | OTHER_IDENTIFIER)

It should be noted that there cannot be any whitespace around the? because <PRIVATE_PUBLIC_IDENTIFIER> is a lexer rule, not a grammar rule. This ensures that there is no ambiguity for cases where a ? is playing another role, and there is some whitespace before or after the ?.

There may be breakage in cases like if (0 < _?myBoolean : true) ... because there is no whitespace around ? and hence _?myBoolean is recognized as a PRIVATE_PUBLIC_IDENTIFIER. However, this is probably rare. A search in internal code brought only one single occurrence, which wasn't code (it was a string literal, r'^_?pubspec\.yaml$').

@eernstg eernstg added the small-feature A small feature which is relatively cheap to implement. label Jul 5, 2024
@eernstg eernstg changed the title Support paired-up private and public variables Support paired-up private and public variables: "Private var, public final" Jul 5, 2024
@AlexanderFarkas
Copy link

AlexanderFarkas commented Jul 5, 2024

To me _?varname is obscure. I would not expect it to mean underscore is optional.

I would much prefer simply allowing underscored named parameters, if it's the only use case for this feature.
Also, if the language allowed referring it with not-underscored name, it would be great.

@eernstg
Copy link
Member Author

eernstg commented Jul 5, 2024

To me _?varname is obscure.

True, it is obscure, at a glance. The idea that "it's just a regular expression" would make it easier to remember what it does, but there will be a need to get to know about this before it will look familiar and readable.

However, I expect that this will be a brief transition, and the construct will then be easy to recognize.

I think it might be used relatively frequently because it seems very meaningful from a software engineering point of view to have this representation of a property such that "we can read-write, but clients can only read".

Granted, it's a very small improvement, but so is super parameters. Small things can make a difference. ;-)

I would much prefer simply allowing underscored named parameters

The original proposal in #2509 uses private names for named initializing formal parameters, and leaves the corresponding public name implicit. This works in that particular case because it's an error to have a named formal whose name is private, so it must be somewhat magic.

I included initializing formals in this proposal because we can then avoid the magic: this._?x arguably shows that we have two names on the table: _x which is the name of the instance variable which is being initialized by this parameter, and x which is used at call sites.

It is not an error for an instance variable to have a private name, so in that case we just can't do it using "magic", and hence I'm proposing that we should use explicit syntax to show that we're dealing with two names.

I think it's going to work just fine that we don't indicate what those two names are doing, because there is only one thing they can meaningfully do in each case.

@jakemac53
Copy link
Contributor

Fwiw, this is the type of thing a macro can easily do. Maybe it would be better to do this some other more built-in way just for efficiency sake, but I don't expect this to be super commonly used.

Granted, it's a very small improvement, but so is super parameters. Small things can make a difference. ;-)

Super parameters are probably much more common though, especially because of how flutter is structured.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
small-feature A small feature which is relatively cheap to implement.
Projects
None yet
Development

No branches or pull requests

3 participants