Open lrhn opened 5 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,..
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.
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.
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.
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.
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:
var
.for
-in
you don't need var
or a type: for (x in ...)
is implicitly final x
. (Ditto for parameters). You can't reuse an existing variable any more (so int? last; for (last in foo); print(last);
won't work, you have to rewrite as int? last; for ($tmp in foo) { last = foo; } print(last);
. I hope nobody knows it ever could work.catch (e, s)
variables are already final)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.
I'd be fine with leaving final
for that specific case of declaring a variable without a type (same as var
today).
I'd be fine with leaving
final
for that specific case of declaring a variable without a type (same asvar
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.
We can spell final
with fewer letters for that case, like val
or let
, sure.
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.
I think final
is fine and easiest way to declared a final auto type
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'
.
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.
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;
I'd be fine with leaving
final
for that specific case of declaring a variable without a type (same asvar
today).I think this misses the point, which is that many want the convenience and shorter notation of
var
, with the benefits offinal
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...
@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;
@leonsenft same cases, sometimes I'm looking for feature like this
var? current = this;
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
.
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...
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.
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.
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.
It would be nice even just to shorten final to fin.
If Dart would go with val
and var
as Scala does, that would be very nice I think.
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
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
Also, I have a question,
Why cannot, const
, and final
; be made automatic, because the analyzer knows, that
const
or not, andthanks again,
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.
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
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.
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.
final
is clearer in stating the intended effect that this variable can't be assigned to again
because final
is semantically much closer to that idea than val
, let
or :=
.
mutable by default allows new developers to be productive and make progress much quicker because they are not forced to use concepts that they really don't need.
I think that syntax should be optimized for reading and not for writing because code is read more often than it is written. I agree that typing two extra characters can be annoying, but when reading code, the two extra characters are really helpful in finding out what's mutable and what's not.
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).
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 💛
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
.
Also:
val
, let
, and var
, val
has the advantage over let
in that it's more appropriate for uninitialized declarations (e.g., instance variables and lazy initialized locals). [Removed] fin
could work, but is less ideal imo due to it being ambiguous - could mean finally
, finalizer
, etc., and also is phonetically quite distinct from final
.:=
is used in Go [Removed], and can be used for untyped initialization when final-by-default is turned on.mut
from Rust can be used to indicate mutability*** for function parameters, de-structured pattern assignments, and typed declarations, when either final-by-default gets turned on for specific scopes (e.g., packages), or globally.[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.
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.
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
I see those two CLs are currently abandoned. What are the next steps for this?
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
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:
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
withval
. 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):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.