Skip to content

Require "final" when using primary constructor syntax in a view declaration #2546

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
munificent opened this issue Oct 1, 2022 · 10 comments
Labels
brevity A feature whose purpose is to enable concise syntax, typically expressible already in a longer form primary-constructors

Comments

@munificent
Copy link
Member

I love the primary constructor syntax in the views proposal. I really want that syntax for classes too. Based on that, I think we should require a final modifier when defining a primary constructor on a view:

view class V(final int x) {
  ...
}

I'm assuming we will eventually do primary constructors for classes. When we do, I believe fields should default to non-final. That's consistent with how both parameter declarations and field declarations work today. It's also consistent with the view proposal that requires you to write final if you declare the representation as an instance field inside the body.

Requiring final is a tiny amount of verbosity and we get consistency and clarity in return. The verbosity doesn't bother me much because I don't anticipate a large number of users authoring view types. A much larger number of them will end up reading them when they go to definition. When they do, since the feature may be unfamiliar, I think it helps for the syntax to be clearer and seeing the final there can help them understand that the underlying representation field can't be assigned by the view.

@lrhn
Copy link
Member

lrhn commented Oct 2, 2022

Annoying, but probably the correct call.

"Final by default" as our next big thing, anyone?

@eernstg
Copy link
Member

eernstg commented Oct 2, 2022

I was thinking that we would include the final modifier implicitly for a view class and for a struct. So with a general notion of primary constructors it would be allowed, but not required. It won't take long for anyone who uses views or structs to learn that their instance variables are all final, and when that's a well known fact it seems unnecessary to require the modifier, just a few tokens after the place where we find view or struct.

The same line of thinking would allow, but not require, each member in a view class to have the modifier extension.

@lrhn
Copy link
Member

lrhn commented Oct 3, 2022

If we say that final is the default for fields in enums, views and structs which are defined by a primary constructor, then it probably won't be that surprising. And constant primary constructors const class Foo(int x) ...!

I'd disallow Foo(var x) then, since it is suggesting a variable variable.

(Or, heck, make them default to final on all classes, so you'd have to put var in front of the variable to get a non-final instance variable from the primary parameter, which you can only do on plain classes.)

Whether I'd want to make the final implicit on explicitly declared instance variables is a different matter.
I'm not sure that's as non-confusing.
Both structs and views are immutable, so obviously their variables must be final. But so are enums and classes with const constructors, but we don't make the fields final by default there. We just give you an error if you forget to write the final.

@eernstg
Copy link
Member

eernstg commented Oct 3, 2022

But we could make the final modifier optional for enums and classes with a constant constructor, too.

I think it's useful to consider the failure mode: When a developer forgets about this rule, or doesn't know the rule in the first place, the outcome is a compile-time error in the case where that developer makes an attempt to modify one of those immutable instance variables. This means that the feedback is immediate when it matters, and no bugs are introduced silently.

This is much less error-prone than, say, a rule that allows an implicitly covariant type variable to be used in a non-covariant position in a member signature. ;-)

I think it won't hurt to consistently make the final modifiers implicit, and consistently have them enabled by default in the cases mentioned above.

@lrhn
Copy link
Member

lrhn commented Oct 3, 2022

I think it's too dangerous to make explicitly declared instance variables final in a class because it has a const constructor.
If it has two const constructors, and you remove either one, nothing changes, but if you remove both, the variables become non-final. That's too much of an effect at a distance.

We could allow adding const in front of the class, const class, and that forcing the class to behave as if it should have a const constructor (fields all final). Whether generative constructors should default to being const then is more doubtful, so all the const would do is to make fields final. And make a primary constructor a const constructor, probably also a default constructor if you don't declare any constructors.

@munificent
Copy link
Member Author

munificent commented Oct 3, 2022

But so are enums and classes with const constructors, but we don't make the fields final by default there. We just give you an error if you forget to write the final.

And, in practice, this doesn't seem to bother many people. I like brevity and I wish non-assignable had always been the default. But it isn't, and that ship has sailed. I think there's something to be said for being consistent and simple even if it means a little verbosity in some cases.

If someone is reading a view type or enhanced enum for the first time, they already have to learn what the view or enum part means. It would be nice if there wasn't this other non-obvious thing they had to learn where primary constructor or field syntax they know from classes looks the same here, but means something different.

On the other hand, flipping the default for struct is pretty compelling, since being a immutable is a major point of the whole feature...

@leafpetersen
Copy link
Member

I don't have strong feelings about this for views (as opposed to structs).

@lrhn
Copy link
Member

lrhn commented Oct 4, 2022

Being immutable is a pretty major point for enum classes too, and some would also say it for const classes (even if it's not enforced deeply). And views have only one field, which is very much immutable.

I don't think structs are particularly special in that regard. They're just one step further removed from a plain class, so the step to make them special-case variable declarations feels smaller.

I think we should keep requiring final in front of explicitly declared fields in enum, const-classes, view and struct declarations.
I never want to see var x; in any of those, and it meaning final x;. Seeing int x; and it meaning final int x; worries me too, only slightly less.

What I do want is special behavior for a primary constructor's implicit fields, which depends on what the underlying construct is.
Those are declared as parameters, and only indirectly define fields. Requiring you to write Foo(final int x) for a struct, view or enum parameter, in order to introduce a necessarily final field, just doesn't seem necessary or beneficial to me.

(And I would introduce const class Foo(int x, int y) which makes the primary constructor const and its fields final.)

So, all in all: I am fine with allowing views to not require final on the primary constructor parameter, and the field will still be "final". (It's not really a field, it's just declared like one, but it works more like an implicit getter int get x => this as int;.)

@munificent
Copy link
Member Author

I never want to see var x; in any of those, and it meaning final x;.

Agreed 1000%.

Requiring you to write Foo(final int x) for a struct, view or enum parameter, in order to introduce a necessarily final field, just doesn't seem necessary or beneficial to me.

That's an interesting way to split it. So basically, if you opt in to using the primary constructor sugar, we try to sweeten it as much as possible. I think that principle probably reasonable extends to also letting you write _foo as the parameter and have its parameter name be foo and field _foo.

But if you write the fields explicitly, they have a uniform syntax across all constructs.

@eernstg
Copy link
Member

eernstg commented Oct 17, 2023

I adjusted the labels: Primary constructors are not expected to be specific to extension-types, they will (if accepted into the language) be applicable to classes, mixin classes, enums, extension types, and perhaps more.

@eernstg eernstg added the brevity A feature whose purpose is to enable concise syntax, typically expressible already in a longer form label Oct 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
brevity A feature whose purpose is to enable concise syntax, typically expressible already in a longer form primary-constructors
Projects
None yet
Development

No branches or pull requests

4 participants