language
language copied to clipboard
The `final` keyword is too long
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.
FWIW, I can report my personal experiences here.
I've been using final inferred variables for some time now (all my repos have the prefer_final_locals
and omit_local_variable_types
lints enabled). For local variables, the overhead is only two characters, and after getting used to it, I hardly ever notice it.
For class instances the picture is similar due to type inference: either the overhead is only two characters (class C { final m = <String, List<int>>{}; }
), or the variable is not initialized on the same line, so there usually is sufficient space for the six addtional characters (class C { final Map<String, List<String>> m; }
).
However, there is one place where I would love to use final variables, but can't bring myself to use the keyword because it is just too verbose: function parameters. The additionaly six characters per parameter clutter the signature with keywords that establish a constraint most programmers assume anyways (see the parameter_assignments
lint), and will regularly break the dartfmt 80 character limit.
So IMHO, this would be the place that would profit most of a more compact final
marker.
lints:
- http://dart-lang.github.io/linter/lints/prefer_final_locals.html
- http://dart-lang.github.io/linter/lints/omit_local_variable_types.html
- http://dart-lang.github.io/linter/lints/parameter_assignments.html
let
could be preferable to val
or :=
for immutable bindings:
val
implies the rhs is a value object Kotlin style, not just a binding.
val
is tricky to distinguish from var
when reading/eyeballing code.
let
is used in kernel.
let
is more pleasant to touch-type on english keyboards than val
.
:=
is less touch-type friendly.
let result = when { }
is aesthetic for expression/functional style programming.
I like final
because it stands out.
let
would be better than val
in this regard.
The distinction between val
and var
is easy to overlook,
but it's easy to switch between var
and val
.
Usually readability should have higher priority.
I think a bigger problem with final
is that it's not the default. I'd rather have to put var
to mean that I want something to be, well, variable, and in the case of local variables I'd want the compiler to scream at me if I don't actually mutate it. So:
String foo = readFoo(); // final by default
final foo = readFoo(); // same thing but infers type
var String foo = readFoo(); // foo is mutable
var foo = readFoo(); // type inference
This would also solve @pschiffmann's problem with function parameters.
Also, I agree with @zoechi that let
is a better keyword for this.
Note that let
is a non-final binding in JavaScript so it can be confusing for people coming from JS.
Dart is enough of a distinct entity to JavaScript that it's usage re let
can differ.
Also, JavaScript devs may appreciate a succinct let
vs a more verbose and misleading const
for immutable bindings.
let
comes from the functional world and functional concepts are gaining dev mindshare.
So let
could be part of a story re introducing more functional and expression orientated elements into Dart.
It'd be neat if you could opt-in to making final
the default.
{$FINALDEFAULT ON}
or
#pragma final_default on
or
.ALLFINAL
or
use final;
or
from __future__ import everything_final
Swift also uses let
and has the same meaning as final
in Dart.
Swift let
is like val
in Kotlin. The rhs is immutable as well as the lhs binding.
Is this proposal about a shorter lhs binding keyword or about a broader story re immutablity?
Personally just think lhs binding is appropriate.
It's about the binding of the new declared name to an object. That object may be mutable. For immutability of instances, check #125.
@mraleph I wouldn't be worried about JS semantics at all. Over there the var
vs let
vs const
distinction is already too confusing to let that language inform what Dart should do.
@peldritch Swift does not make the rhs immutable. The following is valid Swift code:
class Mutable {
var message: String = ""
}
let m = Mutable()
m.message = "What's up?" // totally OK
// What it doesn't let you do is rebind the variable:
m = Mutable() // ERROR
This is also the semantic we want in Dart.
@yjbanov
Right, Swift let
has a rhs effect for struct
but not for class
. So it's moot given Dart doesn't have structs.
let result = when { }
is aesthetic for expression/functional style programming.
Hold on, can Dart do that? :thinking:
I like
final
because it stands out.
Likes and dislikes are not very important for the sane language design, especially if you cannot justify them.
Most variables are "final", and only minority are vars. It is absolutely illogical for the common case to stand out! But for the rare case it may be a desirable property.
It might make more sense to rename var
to mutable
so that people feel bad about writing it down.
(edit: i'm kinda joking, just use let
)
@dlepex
I like
final
because it stands out.
That depends on how much change is possible.
I'd prefer https://github.com/dart-lang/language/issues/136#issuecomment-447940385 any time.
especially if you cannot justify them.
I think I did.
Most variables are "final", and only minority are vars. It is absolutely illogical for the common case to stand out! But for the rare case it may be a desirable property.
There are a few separate concerns here that I think should be teased apart:
-
Most local variables are not re-assigned.
-
The author of the code wants to record their intent that the variable cannot be reassigned.
-
The reader of the code wants to see that intent reflected in the code.
The first point is observably empirically true. But it's not clear to me that the latter two are, at the level of local variables. There are a lot of intents that a programmer could write down for later programmers to know, and some of those could be mechanically verified by the analyzer. For example:
-
We could add an
undef
keyword to mark the end of a scope where a local variable should disappear. Most locals aren't used all the way to the end of a block, and most agree that the smaller the scope a variable has, the easier code is to understand. -
We could require users to add some marker when they refer to a variable declared outside of a lambda. Closures affect the lifetime of the object and are "less local", so maybe it would be helpful to force users to opt into it and see that it's happening.
-
We could make array sizes part of their type, like Pascal does. That lets you statically avoid some array bounds errors.
-
We could make a distinction between strings which can and cannot be empty. Lots of code considers it an error for an empty string to be passed, so why not check that mechanically?
With all of these, the question isn't "is the intent useful?" It's whether the intent useful enough to:
- Ask users to spend the mental effort to decide what their intent is and write it down.
- Ask all users to understand this feature of the language and be able to maintain code that uses it.
- Ask all users to spend time reading code that is more verbose because it expresses this intent.
- Ask users to go back and change their code when their intent changes. For example, if you later decide you do want to assign to some variable, you have to go back and turn the
final
(or whatever) intovar
.
For local variables, it's not clear to me that it's actually a net productivity gain to track which ones can be assigned and which can't. The variable is already local, so its scope is relatively small. It's often only a second's glance to tell if it is reassigned. Most editors will show all uses of a variable when you hover over it.
I do wish we had a shorter keyword for single assignment variables. (I pushed for val
way back before Dart 1.0. Alas.) It would be particularly nice for fields and top level variables. But, for locals, I honestly don't think using var
everywhere causes any measurable harm. It makes it easier when you do want to assign to them, and the fact that it isn't reassigned rarely improves the readability by any noticeable amount.
And, in general, I think it's important that we make a distinction between what the code the user wrote happens to do and what they intend it to be able to do. If I write a class and don't give it a private generative constructor, that doesn't necessarily mean I intend for users to subclass it. I may have simply not bothered to author any intent one way or the other. I think that's likely true of almost all uses of var
for local variables.
In Flutter we've been enforcing the use of final
everywhere (except for
loop variables and arguments, cc @pq) for a while, and I've found it really helpful to know immediately which variables are going to mutate and which are not. Surprisingly so, in fact. It's really confidence-building when reading new code if you can immediately know that a particular variable is not going to change, especially when there's multiple levels of complicated nested loops.
I wish we could opt-in (on a per-file or per-library basis) to making final
the default, with var Foo foo = ...
to declare a non-final field, removing final
from the language.
+1 on the value of seeing at declaration time whether a variable may be reassigned. I certainly can scan down a function looking for assignments, but when I'm trying to wrap my head around new code final
is a shortcut to understanding that is a huge benefit in the code that uses it.
I do not find it a drawback to need to go back and remove a final
(or change a let
to a var
) when I add a new assignment to a variable - it's a reminder that my change isn't as shallow as I may have thought. And the same argument can be made for how easy this is - since the scope is small I shouldn't need to move far to do it.
FYI, just filed related https://github.com/dart-lang/language/issues/160 (if immutable shared objects also support sharing closures then the ergonomics of final
will be even more important).
One wrinkle with pushing towards single assignment locals by default is parameters. Even if we get let
or val
and encourage everyone to use it whenever possible, there's still the question of whether parameters should be single-assignment or not.
-
If we think single-assignment is definitely better, than that's an argument that parameters should be implicitly final.
-
On the other hand, an assignable parameter is strictly more useful than a single-assignment one. If you do want to assign to it, the current behavior already enables that without requiring you to opt in.
Scala, Kotlin, and Swift all treat parameters as single-assignment. They also all had ways to out of that which they then later deprecated and removed so that parameters are only single-assignment. That's a strong signal that it would be the right behavior for Dart if we moved to a shorter local variable keyword and encouraged everyone to use it. However, that would also be a massively breaking change. It's an automatically toolable one, but still. :-/
They also all had ways to out of that which they then later deprecated and removed so that parameters are only single-assignment
Do you have any links to more details on that? I'd love to read about these changes.
Oops, I may have misspoken about Scala. They may have always been single-assignment. Here is some discussion of the changes in Swift and Kotlin.
Those seem like pretty compelling arguments, I'm sold.
The IDE could use syntax highlighting to tell you whether a variable is reassigned, with no help needed from the developer.
This is trivial to implement--I've done it--but unfortunately it requires a new version of the analyzer<-->IDE protocol, so it's breaking change, and the analyzer team wanted to wait for more new features to justify making a breaking change. As far as I'm aware there haven't been any others in ~years, though, so maybe it's time.
IDE's don't help when you're trying to understand the code in a YouTube video or on a slide at a conference or whatnot.
And in GitHub. It also doesn't cause a warning if you assign to a variable that were declared final.
Right. But people use var
or final
without type annotations, and people use unqualified imports instead of show
, which is the same trade-off: it makes something visible only using the analyzer, not directly in the source. So it's not too shocking to lean on the analyzer.
It's fine adding that but if possible not at the expense of other helpful features like final