dart-lang / language

Design of the Dart language
Other
2.67k stars 205 forks source link

The `final` keyword is too long #136

Open lrhn opened 5 years ago

lrhn commented 5 years ago

It's inconvenient that declaring final variables require more typing than non-final variables. This discourages the use of final variables.

Even with inferred types, it's shorter, and if you want to specify the type, it gets even longer:

var foo = ...;
String foo = ...;
final foo = ...;
final String foo = ...;

While final variables are not strictly necessary (if you don't assign to a variable, it doesn't matter semantically whether it's declared final or not), some people prefer a style where you make variables final unless they need to be mutable, and Dart does not support that writing style very well.

See #135 for one suggestion which improves the experience for inferred variables by replacing final with val. It doesn't improve the typed case. It requires adding a new built-in identifier (but likely not a reserved word).

Another option could be using := for final initialization (since final local variables need to be initialized):

foo := ...;
String foo := ...;

This doesn't work for instance variables since they may be intialized by a constructor, or parameters, and to keep requiring the final word for instance variables and parameters, and using := everywhere else, is inconsistent.

cedvdb commented 3 years ago

Correct. After much discussion many of us have settled on always using var for locals; it's why I added the lint unnecessary_final.

using final shouldn't be used as safe guard but as to communicate intent imo. That intent being, this variable shouldn't be reassigned.

However this is digressing from the topic here.

Furthermore there are places in the language where more succinct language would yield more return on investment. To name a few: switch, constructors, data class boiler plate,..

lukepighetti commented 3 years ago

Why this divergence has occurred is not clear to me

I think a lot of Flutter users adopt the style of the Flutter framework itself, which is quite different from the prior idiomatic Dart style.

Or to make it clear which entities in a state class are mutable with setState(), and which entities are 'hard coded', and this bled into their general programming style.

rrousselGit commented 3 years ago

final local variables are not just to match flutter's styleguide

It is an important practice to avoid reassigning local variables and prefer creating a new variable when possible. It's a common readability nightmare in legacy code where a variable is changed in the middle of a 200 lines method.

venkatd commented 3 years ago

We default to using final for all variables. Both for classes and local variables. There's a reduced mental burden when reading code knowing that something can't be reassigned.

It's small, but it did add friction when I was new to dart. Typing var was a little bit easier, so thinking it was no big deal, I went with that.

Hixie commented 3 years ago

dart fix makes it even more plausible that we could literally flip the language around here, making everything final by default (and remove final from the language), and if you want to make something assignable, it has to be marked var.

class Foo {
  Foo(this.id);
  var String value = 'mutable';
  int id; // immutable
  List<Foo> children = <Foo>[]; // list contents are mutable, `children` member is not

  int test(int immutable, var int mutable) {
    for (var int index = 0; index < immutable; index +=1 ) { // ok maybe we should imply `var` here...
      print(index);
    }
    for (Foo child in children) { // `child` is immutable
      mutable += 1;
    }
    return mutable;
  }

It might be worth talking about changing the semantics of the index in a C-style for loop, by the way. Right now they're entirely mutable. Would be good to make them immutable except in the third clause of the for loop declaration by default (and then var could be used to return to today's behaviour).

Anyway the point is that dart fix would let us do this entirely mechanically as a language upgrade, just like we did the null safety migration.

lrhn commented 3 years ago

Removing final means that you have to write a type on all final variables, which can probably get annoying (unless we also introduce the := final assignment, but then we don't actually need to remove final). In general, we do want to have ways to infer types of variables with initializer expressions, even if final.

The full "final by default" proposal that I started at some point had:

The for(;;) loop is tricky in that it's several idioms in one, and not all of them fit. I guess a final variable declaration can be tweaked into a variable variable declaration that cannot be modified outside of the increment part. That's no weirder than what's already going on with variables in for loops.

Hixie commented 3 years ago

I'd be fine with leaving final for that specific case of declaring a variable without a type (same as var today).

leonsenft commented 3 years ago

I'd be fine with leaving final for that specific case of declaring a variable without a type (same as var today).

I think this misses the point, which is that many want the convenience and shorter notation of var, with the benefits of final by default.

My understanding is that Flutter prefers LHS types , but google3 enforces omit_local_variable_types, so ideally we could land on a solution that satisfies both parties.

Hixie commented 3 years ago

We can spell final with fewer letters for that case, like val or let, sure.

tomwyr commented 3 years ago

I'm not sure if there's any advantage in inventing yet another way of declaring immutable object references. As @Hixie mentioned, either val or let would be probably least confusing since both are already present in widely used programming languages like Kotlin, Scala, Rust, Swift.

tbm98 commented 3 years ago

I think final is fine and easiest way to declared a final auto type

Hixie commented 3 years ago

my proposal was intended to match the current status quo, with just the default behaviour flipped. Today you can't say var String a = 'str'.

cedvdb commented 3 years ago

my proposal was intended to match the current status quo, with just the default behaviour flipped. Today you can't say var String a = 'str'.

An analyzer should check the percentage of final vs var (in all its forms, without considering params) that could give some hindsight to see if that makes sens. I personally think it makes sens to be restrictive by default and final String is way too verbose.

cedvdb commented 3 years ago

Also, this opens a way to using two styles of declarations: one with the explicit final, another - with final by default: final int x=42; final x=42; Which is not very good

This is already how it works?

But these two notions ("final" and "type") are totally different.

The point is that writing final String x or final x is supposedly more widely used than either var x or String x. While the two first expressions are used way more than the two later, both are more verbose. Switching the default would give:

final x = 'final';
String x = 'final';
var x = 'var';
var String x = 'v'ar;
willhaslett commented 3 years ago

I'd be fine with leaving final for that specific case of declaring a variable without a type (same as var today).

I think this misses the point, which is that many want the convenience and shorter notation of var, with the benefits of final by default.

My understanding is that Flutter prefers LHS types , but google3 enforces omit_local_variable_types, so ideally we could land on a solution that satisfies both parties.

@leonsenft, tangential to your point, but I find it odd that omit_local_variable_types uses the word "usually" in its rationale. Is it that the worst, rare case is that type inference is doable but expensive? If it can ever fail...

leonsenft commented 3 years ago

@willhaslett

Is it that the worst, rare case is that type inference is doable but expensive?

No, it's because sometimes inference will give you a type you don't want - for example one that's too specific:

abstract class Node {
  Node? get parent;

  Node? ancestorThat(bool Function(Node) matches) {
    var current = this; // Inferred as `Node`.
    while (current != null) {
      if (matches(current)) {
        break;
      }
      current = current.parent; // Error; can't assign `Node?` to `Node`.
    }
    return current;
  }
}

One solution here is to widen the type of current with a type annotation to allow null:

    Node? current = this;
ykmnkmi commented 3 years ago

@leonsenft same cases, sometimes I'm looking for feature like this

var? current = this;
Levi-Lesches commented 3 years ago

I don't think we should make non-final look more confusing than it is today. Final by default, meaning a few extra letters for non-final, is fine, but I don't see the value in a punctuation symbol over var or String.

Wdestroier commented 2 years ago

I'd say final is fine... It is only annoying in Java, because you need to write final var x = ...; Making the var keyword final is strange, it would define a variable that isn't variable! And the programmer would need another keyword to define a variable that is variable. val vs var is too easy to miss when reading the code and let is kind of meaningless...

mateusfccp commented 2 years ago

Making the var keyword final is strange, it would define a variable that isn't variable!

This isn't true... A final variable is not immutable, it's only not reassignable. You can still, for instance, make a final a = <int>[] and then call a.add(0). The final variable will vary.

Wdestroier commented 2 years ago

The final variable will vary.

The variable won't vary, what will vary is the class instance field value.

class Text { // Text is a class that explicitly extends
  // the Object class, thus it is an object.
  String value; // value is a field, String is the field type.

  Text(this.value);
}

void main() {
  // name is a variable.
  var name;
  // new Text is a class instance
  name = Text('Gates');

  // The Text class instance is not immutable.
  // Because it contains fields that can have their value changed.
  (name as Text).value = 'Steve';
}

Take as example the following line, which is valid code in Dart:

var name;

This line declares a variable reference called "name". This variable may reference a mutable or an immutable object instance. Example:

var name = const Text('John'); // the var keyword is not related to what it references.

If the var keyword was final, then the line above would declare a final reference to an immutable object. Nothing would be variable at all.

lrhn commented 2 years ago

Depending on context, the word "immutable" often carries connotations of deep immutability. I think it's fair to point out that final variables are not that kind of immutable. They're what we have elsewhere called "unmodifiable". A const variable is deeply immutable, on the other hand. (Well, structurally, not necessarily logically, if it pretends to have mutable state using expandos).

That said, we're talking about Dart final variables here, and their meaning is well defined.

Squinnb commented 2 years ago

It would be nice even just to shorten final to fin.

He-Pin commented 2 years ago

If Dart would go with val and var as Scala does, that would be very nice I think.

cedvdb commented 2 years ago

val is too close to var lexically.

I don't think it needs to be shortened personally, it's quite expressive as is. I'd be fine with it be the default but it does not really matter to me except for parameters.

tangential to your point, but I find it odd that omit_local_variable_types uses the word "usually" in its rationale. Is it that the worst, rare case is that type inference is doable but expensive? If it can ever fail...

Maybe for cases where you do want super type. eg Pattern x = '...' instead of var x = '...'' because you might reassign. In the context of final this does not make sense.

The case where I use both is with late, and it's quite long indeed late final Widget

gintominto5329 commented 1 year ago

Hello,

I think dart 3 is a good opportunity to move to "final by default",

And instead of promoting dart 3, as a mere null-safety release, we would be eligible for the full fledged "safer language" promotion,

Also i read in some issue (on any one of dart/sdk or dart/lang or flutter, repo)(im unable to find it now), that

Dart is promoted as a client optimized language

Which does NOT sound like great, and actually makes the first impression, maybe not bad, but less good, than something like "Safer language"(a claim, backed by final-by-default, and sound-null-safety),

just my opinion, thanks

gintominto5329 commented 1 year ago

Also, I have a question,

Why cannot, const, and final; be made automatic, because the analyzer knows, that

thanks again,

lrhn commented 1 year ago

Too late for Dart 3 to add another major feature.

I too would vote for "final by default", as I've suggest up-thread, but it's not a small change. It requires migrating all code in the world. (Again!) It should be completely automatable, at least, unlike null safety, which also suggests that it doesn't actually change anything except the default. There is no new power, no new protection, like there was with null safety. That lowers the benefit in the cost/benefit ratio.

Making final automatic when a variable is not assigned to is ... basically status quo. If you add an assignment, the variable would stop being final. Whether the variable is final or not should not have any effect if there is no assignment. You can't distinguish "implicitly final if not reassigned" from just plain var.

Making const automatic when calling a const constructor with constant arguments is possible, but has some sharp edges. First of all, it doesn't matter for anything except canonicalization of constant values. There is no discernible semantic difference between a new ConstConstructor(constArgument) and const ConstConstructor(constArgument) other than the identity of the object - the latter is identical to other similar const invocations. The real difference is memory consumption or churn, if you create a lot of new objects.

Also, whether const can be omitted gets tricky around collection literals.

var c1 = ConstantConstructor(const []); // Would be implicitly `const`?
var c2 = const ConstantConstructor([]); // Is explicitly `const`.
var c3 = ConstantConstructor([]); // Would not be implicitly `const`, because the argument is not constant.

It would be easy to take a big constant structure like var c = const Something(Big(Long(Deep(42, [], "banana"))); and think you could remove the const. Or have a big structure that is implicitly const, forget that it's not a const context, and add an empty list like [] somewhere, and suddenly the entire structure becomes non-constant.

And you wouldn't notice, because it still runs, it just allocates a lot more, mostly indistinguishable, objects.

We considered this at some point, and decided that the risks were too big, and it's better to make you always write at least one const when you want to create a constant object. It's worse if you must write it sometimes, and don't have to in other cases, and the only difference can be a single two-character change deep in a large structure.

whoizit commented 1 year ago

final is too long, but I hate := this is why I dislike that

let from rust and immutable by default sounds good for me. Or even fin

myConsciousness commented 1 year ago

Maybe this topic has already changed topics, but based on the title I am writing my opinion.

When I read this title, I thought it would be nice if the variables were final from the beginning, but then I would have to write the type instead of final, forcing even more strange declarations.

On the other hand, I would not like to see strange syntax added. (:=? Please don't!) I like Dart's final declaration very reasonably.

modulovalue commented 1 year ago

If we take this issue to the extreme, I think an argument could be made that other keywords like extension, required and Function are also too long. We could abbreviate extension to ext (6 characters saved) required to req (5 characters saved) and Function to <- (6 characters saved). When should we stop?

Here's perhaps an unpopular opinion: I think that the approach that Dart takes is far superior to all the alternatives proposed in this issue.


I agree that the ability to write those keywords quicker would make developers slightly more productive when writing code. But if the goal is to reduce keystrokes, then I'd recommend the pattern that is known as the hyper key on macos (hyper key via karabiner elements / hyper key via better touch tool). It allows you to reassign caps lock to a new modifier. That way you can get your keystrokes down to 2 keys (e.g. hyper+f to type final, hyper+r to type required, hyper+n to type Function and so on).

rchoi1712 commented 1 year ago

Was led here via #3231 ..

There seems to be some deadlock on this issue, and I agree with others final for local variable initialization is tedious to read, and yet is the desired intent 99% of the time.

So, how about adopting the := syntax as optional in addition to the var keyword? So var a := 1 will be a final initialization?

It’s visually unobtrusive, opt-in, and importantly, looks cool :)

var a = 1;
var b := 2;
var c = 3;

foobar(){
   print(a);
}

. . . P.S. thanks for continuing to improve the language for all of us 💛

rchoi1712 commented 1 year ago

Another option could be using := for final initialization (since final local variables need to be initialized):

foo := ...;
String foo := ...;

This doesn't work for instance variables since they may be intialized by a constructor, or parameters, and to keep requiring the final word for instance variables and parameters, and using := everywhere else, is inconsistent.

This would be similar to the approach taken by Go, with short variable declarations. Those are only allowed in function blocks, also.

Thinking about it more, I prefer this approach, as it doesn't modify any existing language concepts (like var) and provides a nice shorthand, compared even to current declarations with var.

rchoi1712 commented 1 year ago

Also:

[Removed ]


** More analytically, one could argue the compiler is the true audience for the var, val and final keywords. Devs really just want to write-and-forget, and have the compiler spit out error messages when code tries to do the 'wrong' thing, i.e., write to an immutable, or declare a mutable variable in a deeply immutable class.

** I believe mut is better than var for this task, because the term 'variable' in programming actually has no connotation of mutability, and to the extent var means both* 'variable' and 'mutable', it is a conflated term that - not surprisingly - seems to be causing some confusion.

ds84182 commented 1 year ago

Not a fan of :. Not very searchable, and variable shadowing across statement blocks would have to become an error to avoid very obvious footguns.

val is better suited for this. let may conflict with let expressions.

rchoi1712 commented 1 year ago

val is better suited for this. let may conflict with let expressions.

Let expressions - as defined by the team, are great, and will help in the move away from final.

If the team thinks val (as proposed in #3298) is worth experimenting with, I have these CL's ready for review:

https://dart-review.googlesource.com/c/sdk/+/322841 https://dart-review.googlesource.com/c/sdk/+/322842

johnpyp commented 6 months ago

I see those two CLs are currently abandoned. What are the next steps for this?

mcmah309 commented 3 months ago

Adding let might be a good opportunity to allow shadowing of variables. Which would give it additional uses/semantic meaning compared to final. e.g. assume transformation returns a different type, and reusing x/y name is preferred.

let x = func();
let x = transformation(x);
// vs having to do
final y = func();
final yTransfomed = transformation(y);

Related issues: https://github.com/dart-lang/language/issues/1514 https://github.com/dart-lang/language/issues/3322 https://github.com/dart-lang/language/issues/2703