Language: Can `late` alone be used to declare a variable?

Created on 18 Apr 2019  Â·  10Comments  Â·  Source: dart-lang/language

We propose to add a keyword late for marking late initialized variables. This means that for the following declarations:

int x;
final int x;

there are corresponding late variable declarations as follows:

late int x;
late final int x;

What is the late declaration that corresponds to:

var x;

Is it

late var x;

or

late x;

The former is consistent with the treatment of covariant, the latter with the treatment of final.

Thoughts?

cc @munificent @lrhn @eernstg @danrubel @stereotype441 @bwilkerson

nnbd

Most helpful comment

Ok, per discussion:

  • late is a modifier
  • late comes before final, var or <type>

All 10 comments

I'm split here. My personal preference is to go with late x; over late var x; because it should act like final. That's the kind of modifier it is.
I do fear that that might be confusing to some people who have learned that var is what you use to introduce a variable. It's the same as final, so maybe it's not an issue.

It's likely a moot point without an initializer expression because a late x;/late var x; would have type dynamic and be nullable, and not need to be late at all.

A more interesting case is late x = foo(); vs late var x = foo(); where foo introduces a type.
Writing late var x = foo(); still feels like overkill to me.

I agree. Given that late is more similar to final than it is to covariant, it seems like it ought to be consistent with final. And it's a nice bonus that it happens to be more concise.

I don't know if I have a coherent take on this, but here's a brain dump:

Allowing it would make it sort of consistent with final. But I think the way final works is quite confusing. It's a modifier, but also it's a declarator.

This dual nature was one of the sticking points with option semicolons:

final veryLongIdentifier
    anotherLongIdentifier // <-- Part of previous line or not?

We get a lot of feedback from users that they don't like final as a declarator. Much of that is that it's long, but I also think it's partially because it doesn't read like a declarator to them like let or val does. I think the late keyword is even less likely to read like a declarator to someone because there's nothing "variable-like" about that word and it's not used in any other language that I know of at all, much less to declare a variable.

In something simple like:

late x;

Sure, maybe it's pretty obviously some kind of variable declaration thing because there's only a name after it. But if we add destructuring, it gets weird:

late [x, y] = someFunction();

It's hard for me to look at that and not find it very opaque. (I don't know if we will support late destructured variables, but I'm assuming here that we might.)


Note that allowing late x still doesn't make it totally consistent with final. The two aren't parallel constructs because they can be composed. And when they're composed, only a certain proscribed order is allowed.

Also, you can use final for parameters, but not late.


I am also worried that inferring late x to mean non-final encourages the wrong practice. Most other languages are leaning heavily towards immutability. We can't (easily) undo the damage we already did with final being longer than var and typed variables defaulting to immutable, but I'm not crazy about getting even farther in the hole:

late x;
late final x; // 6 characters longer. :(

The way I think about features like this that introduce pretty novel (relative to the larger software world) behavior is that we should err on the side of explicitness. Users pick up syntax much quicker than semantics (ex: initializing formals versus library privacy). So if we have some new, interesting behavior, I want the syntax to be as simple as possible and telegraph it clearly.

I think it's simple and clear to say "'late' is a modifier you can put before a variable declaration".


One way to think about the cognitive load of this is to look at the grammar. Right now (simplified, ignore multi-variable declarations), the grammar is something like:

variable = declarator name ( "=" initializer )? ";" ;

declarator = "var"
           | "final"
           | "final"? type ;

If we treat late as purely a modifier, it becomes:

variable = "late"? declarator name ( "=" initializer )? ";" ;

declarator = "var"
           | "final"
           | "final"? type ;

If we allow it as a declarator or a modifier, I think it's:

variable = declarator name ( "=" initializer )? ";" ;

declarator = "var"
           | "late"? "final"
           | "late"
           | "late"? "final"? type ;

That feels hairier to me once you expand out all the ? to think about which combinations actually are and aren't allowed. I'm assuming here that we explicitly disallow late var x.


We don't treat static or covariant like final. You can't do:

class Foo {
  static x = 123;
  bar(covariant y) {}
}

We also don't treat required like final. Of course, you can do this:

foo({required x});

But you can also do:

foo({required var x});

(I don't know why we allow var for parameters, but we do.)

So I don't know if it's very compelling to say that late should behave like final. We already have other keywords that come before variables that don't work like final and they seem fine.


I don't know if any of this is compelling, but I can't shake the feeling that treating late like a declarator and not simply a modifier is the wrong choice.

If we allow it as a declarator or a modifier, I think it's:

variable = declarator name ( "=" initializer )? ";" ;

declarator = "var"
           | "late"? "final"
           | "late"
           | "late"? "final"? type ;

This isn't actually how the grammar is (among other things, it ignores const), but even so, this is more complicated than it needs to be:

variable = declarator name ( "=" initializer )? ";" ;

 declarator = 
              "var"
            | type
            | "final" type?
            | "late" "final"? type? ;

vs

variable = late? declarator name ( "=" initializer )? ";" ;

 declarator = 
              "var"
            | type
            | "final" type?

I think the former is about as clear as the latter.

I think also think we can allow final late without making the grammar any harder to parse. Even if parsers prefer a fixed order, seeing the first of final and late can make them use different paths for the rest.

Making the parser accept these modifiers in either order is trivial.

But in my mind the bigger question is whether we want to treat these
modifiers differently than other modifiers. The grammar currently specifies
a single order for modifiers everywhere else; as far as I know this would
be the first place the user is given a choice. You can't, for example,
write final static int zero = 0;. (And if we _do_ give users a choice,
the style guide will likely recommend a standard order.)

On Fri, Apr 26, 2019 at 6:43 AM Lasse R.H. Nielsen notifications@github.com
wrote:

I think also think we can allow final late without making the grammar any
harder to parse. Even if parsers prefer a fixed order, seeing the first of
final and late can make them use different paths for the rest.

declarator ::=

"var"

| type

| "final" "late"? type?

| "late" "final"? type?

(I still prefer final late over late final, but I'm willing to allow both
😄)

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/dart-lang/language/issues/321#issuecomment-487061744,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABMR5KZSQQGOBJPWLXNKAYTPSMBJJANCNFSM4HG7FFYQ
.

We only allow one order, and the order is generally from more impactful to less.
The more something affects the API, the earlier it is (except the name should probably be before the type with that argument).

So static is always first because it completely changes how the member is accessed. In the mental ordering, we split statics from instance members before thinking about what those members are.
A final variable only introduces a getter, a non-final introduces a geter and setter, so it affects the presence of a member. Then the type just restricts the allowed values.

For late, it is really at the same level as final, and it depends on the intended behavior whether it's more or less specific.

If you do late final int x; (a write-once integer) and allows users to initialize the variable, then late is probably more important than final.
If you do final late buffer = StringBuffer(); (write-zero, lazy initialization) and just use late for lazy initialization, then final is more important, and late is an implementation detal (at the level of an async marker).
If you have the "slightly late initi" like:

final late String name;
final late int age;
if (something) {
   name = ...
   age = ...
} else {
   name = ...
   age = ...
}

then the late-ness is very localized, and the remainder of the life-time of the varible should just treat it as final.

That's why I think there isn't one answer which fits all use-cases. I expect the publicly late write-once case to be rare compared to the lazy initialization, and I prefer final late to late final for that.

Just wondering, why to use 'late' rather than 'lateinit'? Most developers are comfortable with that already.
Thank you!

A few reasons:

  • late is an existing word whose meaning people know, lateinit is a term coined by Kotlin and only meaningful to the subset of programmers who've used that language.

  • late is half the length.

  • late in Dart is not just for initialization. It doesn't have the same semantics as lateinit.

maybbe late for non-final and latef or late: for final late?
late final int x; is really long.

I would rather type something like :int x = 0 or int x := 0 for final and late int x: for a late final.

Ok, per discussion:

  • late is a modifier
  • late comes before final, var or <type>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

har79 picture har79  Â·  5Comments

creativecreatorormaybenot picture creativecreatorormaybenot  Â·  3Comments

eernstg picture eernstg  Â·  5Comments

munificent picture munificent  Â·  5Comments

kevmoo picture kevmoo  Â·  3Comments