'Florian Loitsch' via Dart Misc
2017-11-10 18:13:00 UTC
I will be traveling next week. The next newsletter is therefore only in two
weeks.
Github link for this newsletter:
https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md
Earlier newsletters:
https://github.com/dart-lang/sdk/tree/master/docs/newsletter
Dart Language and Library Newsletter
Welcome to the Dart Language and Library Newsletter.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#did-you-know>Did
You Know?
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#constructors>
Constructors
Dart has many ways to make writing constructors easier or more powerful.
The most known is probably the concise syntax for initializing instance
fields directly in the signature line (see below). This section shows some
other, less known features.
// Concise syntax for initializing fields while declaring parameters.class A {
final int x;
A(this.x);
}
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#generative-constructors>Generative
Constructors
A constructor is "generative", if it is called on a freshly created
instance to initialize the object. This sounds complicated, but just
describes the behavior of the most common constructors.
class A {
int x;
A(int y) : this.x = y + 2;
}
When a user writes new A(), conceptually, the program first instantiates an
uninitialized object of type A, and then lets the constructor initialize it
(set the field x).
The reason for this wording is, that generative constructors can be used in
super calls in initializer lists. When called as super the generative
constructor doesn't instantiate a new object again. It just does its part
of the initialization.
class A {
int x;
A(int y) : this.x = y + 2;
}
class B extends A {
B(int z) : super(z - 1) {
print("in B constructor");
}
}
The order of evaluation is well defined: first all expressions in the
initializer list are evaluated. Then the initializer list of the super
constructor is run. This continues, until Object (the superclass of every
class) is reached. Then, the bodies of the constructors are executed in
reverse order, first starting the one from Object (not doing anything), and
working its way down the class hierarchy.
This evaluation order is usually not noticeable, but can be important when
the expressions have side-effects, and/or the bodies read final fields:
int _counter = 0;
class A {
final int aCounter;
A() : aCounter = _counter++ {
print("foo: ${foo()}");
}
}
class B extends A {
final int bCounter;
final int field;
B()
: field = 499,
bCounter = _counter++ {
print("B");
}
int foo() => field;
}
main() {
var b = new B();
print("aCounter: ${b.aCounter}");
print("bCounter: ${b.bCounter}");
}
Running this program yields:
foo: 499
B
aCounter: 1
bCounter: 0
Note that the bCounter expression is evaluated first, yielding 0, and that
aCounter, coming second, is set to 1. Furthermore, the final field field in
B is set to 499 when the constructor in A indirectly accesses the field.
Dart guarantees that final fields are only visible with their final value.
Dart ensures this property by splitting the construction of objects into
two: the initializer list, and the constructor body. Without this two-phase
initialization Dart wouldn't be able to provide this guarantee.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#factory-constructors>Factory
Constructors
Factory constructors are very similar to static functions, except that they
can be invoked with new. They don't work on an instantiated (uninitialized)
object, like generative constructors, but they must create the object
themselves.
The following example shows how Future.microtask could be implemented with
a factory and the existing Completerclass.
class Future<T> {
factory Future.microtask(FutureOr<T> computation()) {
Completer c = new Completer<T>();
scheduleMicrotask(() { ... c.complete(computation()) ... });
return c.future;
}
}
The actual implementation uses private classes to be more efficient, but is
otherwise very similar to this code.
Factory constructors cannot be used as targets of super in initializers.
(This also means that a class that only has factory constructors cannot be
extended).
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#redirecting-generative-constructor>Redirecting
Generative Constructor
When constructors want to share code it is often convenient to just forward
from one constructor to another one. This can be achieved with factory
constructors,
but if the constructor should also be usable as the target of a
super-initializer
call, then factory constructors (as described above) are not an option. In
this case, one has to use redirecting generative constructors:
class Point {
final int x;
final int y;
Point(this.x, this.y);
}
class Rectangle {
int x0;
int y0;
int x1;
int y1;
Rectangle.coordinates(this.x0, this.y0, this.x1, this.y1);
Rectangle.box(Point topLeft, int width, int height)
: this.coordinates(topLeft.x, topLeft.y, topLeft.x + width,
topLeft.y.height);
}
class Square extends Rectangle {
Box(Point topLeft, int width) : super.box(topLeft, width, width);
}
The Rectangle class has two constructors (both generative): coordinates and
box. The box constructor redirects to the coordinates constructor.
As can be seen, a subtype, here Square, can still use the constructor in
the initializer list.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#redirecting-factory-constructors>Redirecting
Factory Constructors
Frequently, factory constructors are just used to instantiate a differently
named class. For example, the Iterable class is actually abstract and a new
Iterable.empty() can't therefore be generative but must be a factory. With
factory constructors this could be implemented as follows:
abstract class Iterable<E> {
factory Iterable.empty() {
return new _EmptyIterable<E>();
}
}
There are two reasons, why we are not happy with this solution:
1. there is an unnecessary redirection: the compilers need to inline the
factory constructor, instead of seeing directly that a new
Iterable.empty() should just directly create an _EmptyIterable. (Our
compilers inline these simple constructors, so this is not a real problem
in practice).
2. A factory constructor with a body cannot be const. Clearly, there is
code being executed (even if it's just new _EmptyIterable()), which is
not allowed for const constructors.
The solution is to use redirecting factory constructors:
abstract class Iterable<E> {
const factory Iterable.empty() = _EmptyIterable<E>;
}
Now, the Iterable.empty() constructor is just a synonym for
_EmptyIterable<E>. Note that we don't even need to provide arguments to the
_EmptyIterable<E> constructor. They *must* be the same as the one of the
redirecting factory constructor.
Another example:
class C {
final int x;
final int y;
const C(this.x, this.y);
factory const C.duplicate(int x) = _DuplicateC;
}
class _DuplicateC implements C {
final int x;
int get y => x;
const _DuplicateC(this.x);
}
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#shorter-final-variables>Shorter
Final Variables
In Dart it is now easier to declare mutable locals, than to declare
immutable variables:
var mutable = 499;final immutable = 42;
Declaring a variable as mutable, but not modifying it, isn't a real problem
per se, but it would be nice, if the var keyword actually expressed the
intent that the variable will be modified at a later point.
We recently looked at different ways to make immutable locals more
appealing. This section contains our proposal.
Instead of using a different keyword (like val) we propose to use an even
shorter syntax for immutable locals: colon-equals (:=).
In this proposal, a statement of the form identifier := expression; introduces
a new *final* local variable.
// DateTime.toString() method.
String toString() {
y := _fourDigits(year);
m := _twoDigits(month);
d := _twoDigits(day);
h := _twoDigits(hour);
min := _twoDigits(minute);
sec := _twoDigits(second);
ms := _threeDigits(millisecond);
us := microsecond == 0 ? "" : _threeDigits(microsecond);
if (isUtc) {
return "$y-$m-$d $h:$min:$sec.$ms${us}Z";
} else {
return "$y-$m-$d $h:$min:$sec.$ms$us";
}
}
As a first reaction, it feels dangerous to just use one character (":") to
introduce a new variable. In our experiments this was, however, not an
issue. In fact, single-character modifiers of = are already common: x += 3 is
also just one character on top of = and we are not aware of any readability
issues with compound assignments. Furthermore, syntax highlighting helps a
lot in ensuring that these variable declarations aren't lost in the code.
We would also like to support typed variable declarations: Type identifier
:= expression. (The following examples are just random variable
declarations of our codebase that have been rewritten to use the new
syntax).
int pos := value.indexOf(":");JSSyntaxRegExp re :=
pattern;IsolateEmbedderData ied := isolateEmbedderData.remove(portId);
For now, we are only looking at the := syntax for local variables. If it
proves to be successful, we will investigate whether we should allow the
same syntax for final (global) statics or fields.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#for-loops>For
Loops
For loops are another place where users frequently declare new variables.
There, we need to pay a bit more attention. For example, the for-in
statement doesn't even have any assignment symbol, which we could change to
:=.
When looking at uses of for-in, we found that these loops are almost never
used without introducing a loop variable:
var x;for (x in [1, 2]) {
print(x);
}
In fact, the only cases where we found this pattern was in our own tests...
We thus propose to change the meaning of for (identifier in Iterable). It
should become syntactic sugar for for (final identifier in Iterable).
Note that Dart already supports final identifier in for-in loops, since
each iteration has its own variable. This can be seen in the following
example:
main() {
var funs = [];
for (final x in [1, 2, 3]) { // With or without `final`.
funs.add(() => x);
}
funs.forEach((f) => print(f())); // => 1 2 3
}
With the new syntax the final keyword wouldn't be necessary in this example.
Finally, we also had a look at for. Similar to for-in, a for loop, already
now, does not reuse the loop variable, but introduces a fresh variable for
each iteration.
main() {
var funs = [];
for (int i = 0; i < 3; i++) {
funs.add(() => i);
}
funs.forEach((f) => print(f())); // => 0 1 2
}
This means that there is already syntactic sugar happening to make this
happen. It is thus relatively straightforward to support a version where a
loop variable introduced with := is final within the body of the loop.
main() {
var funs = [];
for (i := 0; i < 3; i++) {
funs.add(() => i);
}
funs.forEach((f) => print(f())); // => 0 1 2
}
This would be (roughly) equivalent to:
main() {
var funs = [];
var i_outer;
for (i_outer = 0; i_outer < 3; i_outer++) {
i_inner := i_outer;
funs.add(() => i_inner);
}
funs.forEach((f) => print(f())); // => 0 1 2
}
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#summary>
Summary
We are investigating ways to make the declaration of final locals easier.
In this proposal we suggest the use of := as new syntax to concisely
declare a fresh final local.
We also propose changes to the for and for-in statements to make the
declaration of final variables concise. The for loop would support the
:= syntax,
and a for-in statement without var or type would implicitly introduce a
fresh final variable.
weeks.
Github link for this newsletter:
https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md
Earlier newsletters:
https://github.com/dart-lang/sdk/tree/master/docs/newsletter
Dart Language and Library Newsletter
Welcome to the Dart Language and Library Newsletter.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#did-you-know>Did
You Know?
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#constructors>
Constructors
Dart has many ways to make writing constructors easier or more powerful.
The most known is probably the concise syntax for initializing instance
fields directly in the signature line (see below). This section shows some
other, less known features.
// Concise syntax for initializing fields while declaring parameters.class A {
final int x;
A(this.x);
}
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#generative-constructors>Generative
Constructors
A constructor is "generative", if it is called on a freshly created
instance to initialize the object. This sounds complicated, but just
describes the behavior of the most common constructors.
class A {
int x;
A(int y) : this.x = y + 2;
}
When a user writes new A(), conceptually, the program first instantiates an
uninitialized object of type A, and then lets the constructor initialize it
(set the field x).
The reason for this wording is, that generative constructors can be used in
super calls in initializer lists. When called as super the generative
constructor doesn't instantiate a new object again. It just does its part
of the initialization.
class A {
int x;
A(int y) : this.x = y + 2;
}
class B extends A {
B(int z) : super(z - 1) {
print("in B constructor");
}
}
The order of evaluation is well defined: first all expressions in the
initializer list are evaluated. Then the initializer list of the super
constructor is run. This continues, until Object (the superclass of every
class) is reached. Then, the bodies of the constructors are executed in
reverse order, first starting the one from Object (not doing anything), and
working its way down the class hierarchy.
This evaluation order is usually not noticeable, but can be important when
the expressions have side-effects, and/or the bodies read final fields:
int _counter = 0;
class A {
final int aCounter;
A() : aCounter = _counter++ {
print("foo: ${foo()}");
}
}
class B extends A {
final int bCounter;
final int field;
B()
: field = 499,
bCounter = _counter++ {
print("B");
}
int foo() => field;
}
main() {
var b = new B();
print("aCounter: ${b.aCounter}");
print("bCounter: ${b.bCounter}");
}
Running this program yields:
foo: 499
B
aCounter: 1
bCounter: 0
Note that the bCounter expression is evaluated first, yielding 0, and that
aCounter, coming second, is set to 1. Furthermore, the final field field in
B is set to 499 when the constructor in A indirectly accesses the field.
Dart guarantees that final fields are only visible with their final value.
Dart ensures this property by splitting the construction of objects into
two: the initializer list, and the constructor body. Without this two-phase
initialization Dart wouldn't be able to provide this guarantee.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#factory-constructors>Factory
Constructors
Factory constructors are very similar to static functions, except that they
can be invoked with new. They don't work on an instantiated (uninitialized)
object, like generative constructors, but they must create the object
themselves.
The following example shows how Future.microtask could be implemented with
a factory and the existing Completerclass.
class Future<T> {
factory Future.microtask(FutureOr<T> computation()) {
Completer c = new Completer<T>();
scheduleMicrotask(() { ... c.complete(computation()) ... });
return c.future;
}
}
The actual implementation uses private classes to be more efficient, but is
otherwise very similar to this code.
Factory constructors cannot be used as targets of super in initializers.
(This also means that a class that only has factory constructors cannot be
extended).
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#redirecting-generative-constructor>Redirecting
Generative Constructor
When constructors want to share code it is often convenient to just forward
from one constructor to another one. This can be achieved with factory
constructors,
but if the constructor should also be usable as the target of a
super-initializer
call, then factory constructors (as described above) are not an option. In
this case, one has to use redirecting generative constructors:
class Point {
final int x;
final int y;
Point(this.x, this.y);
}
class Rectangle {
int x0;
int y0;
int x1;
int y1;
Rectangle.coordinates(this.x0, this.y0, this.x1, this.y1);
Rectangle.box(Point topLeft, int width, int height)
: this.coordinates(topLeft.x, topLeft.y, topLeft.x + width,
topLeft.y.height);
}
class Square extends Rectangle {
Box(Point topLeft, int width) : super.box(topLeft, width, width);
}
The Rectangle class has two constructors (both generative): coordinates and
box. The box constructor redirects to the coordinates constructor.
As can be seen, a subtype, here Square, can still use the constructor in
the initializer list.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#redirecting-factory-constructors>Redirecting
Factory Constructors
Frequently, factory constructors are just used to instantiate a differently
named class. For example, the Iterable class is actually abstract and a new
Iterable.empty() can't therefore be generative but must be a factory. With
factory constructors this could be implemented as follows:
abstract class Iterable<E> {
factory Iterable.empty() {
return new _EmptyIterable<E>();
}
}
There are two reasons, why we are not happy with this solution:
1. there is an unnecessary redirection: the compilers need to inline the
factory constructor, instead of seeing directly that a new
Iterable.empty() should just directly create an _EmptyIterable. (Our
compilers inline these simple constructors, so this is not a real problem
in practice).
2. A factory constructor with a body cannot be const. Clearly, there is
code being executed (even if it's just new _EmptyIterable()), which is
not allowed for const constructors.
The solution is to use redirecting factory constructors:
abstract class Iterable<E> {
const factory Iterable.empty() = _EmptyIterable<E>;
}
Now, the Iterable.empty() constructor is just a synonym for
_EmptyIterable<E>. Note that we don't even need to provide arguments to the
_EmptyIterable<E> constructor. They *must* be the same as the one of the
redirecting factory constructor.
Another example:
class C {
final int x;
final int y;
const C(this.x, this.y);
factory const C.duplicate(int x) = _DuplicateC;
}
class _DuplicateC implements C {
final int x;
int get y => x;
const _DuplicateC(this.x);
}
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#shorter-final-variables>Shorter
Final Variables
In Dart it is now easier to declare mutable locals, than to declare
immutable variables:
var mutable = 499;final immutable = 42;
Declaring a variable as mutable, but not modifying it, isn't a real problem
per se, but it would be nice, if the var keyword actually expressed the
intent that the variable will be modified at a later point.
We recently looked at different ways to make immutable locals more
appealing. This section contains our proposal.
Instead of using a different keyword (like val) we propose to use an even
shorter syntax for immutable locals: colon-equals (:=).
In this proposal, a statement of the form identifier := expression; introduces
a new *final* local variable.
// DateTime.toString() method.
String toString() {
y := _fourDigits(year);
m := _twoDigits(month);
d := _twoDigits(day);
h := _twoDigits(hour);
min := _twoDigits(minute);
sec := _twoDigits(second);
ms := _threeDigits(millisecond);
us := microsecond == 0 ? "" : _threeDigits(microsecond);
if (isUtc) {
return "$y-$m-$d $h:$min:$sec.$ms${us}Z";
} else {
return "$y-$m-$d $h:$min:$sec.$ms$us";
}
}
As a first reaction, it feels dangerous to just use one character (":") to
introduce a new variable. In our experiments this was, however, not an
issue. In fact, single-character modifiers of = are already common: x += 3 is
also just one character on top of = and we are not aware of any readability
issues with compound assignments. Furthermore, syntax highlighting helps a
lot in ensuring that these variable declarations aren't lost in the code.
We would also like to support typed variable declarations: Type identifier
:= expression. (The following examples are just random variable
declarations of our codebase that have been rewritten to use the new
syntax).
int pos := value.indexOf(":");JSSyntaxRegExp re :=
pattern;IsolateEmbedderData ied := isolateEmbedderData.remove(portId);
For now, we are only looking at the := syntax for local variables. If it
proves to be successful, we will investigate whether we should allow the
same syntax for final (global) statics or fields.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#for-loops>For
Loops
For loops are another place where users frequently declare new variables.
There, we need to pay a bit more attention. For example, the for-in
statement doesn't even have any assignment symbol, which we could change to
:=.
When looking at uses of for-in, we found that these loops are almost never
used without introducing a loop variable:
var x;for (x in [1, 2]) {
print(x);
}
In fact, the only cases where we found this pattern was in our own tests...
We thus propose to change the meaning of for (identifier in Iterable). It
should become syntactic sugar for for (final identifier in Iterable).
Note that Dart already supports final identifier in for-in loops, since
each iteration has its own variable. This can be seen in the following
example:
main() {
var funs = [];
for (final x in [1, 2, 3]) { // With or without `final`.
funs.add(() => x);
}
funs.forEach((f) => print(f())); // => 1 2 3
}
With the new syntax the final keyword wouldn't be necessary in this example.
Finally, we also had a look at for. Similar to for-in, a for loop, already
now, does not reuse the loop variable, but introduces a fresh variable for
each iteration.
main() {
var funs = [];
for (int i = 0; i < 3; i++) {
funs.add(() => i);
}
funs.forEach((f) => print(f())); // => 0 1 2
}
This means that there is already syntactic sugar happening to make this
happen. It is thus relatively straightforward to support a version where a
loop variable introduced with := is final within the body of the loop.
main() {
var funs = [];
for (i := 0; i < 3; i++) {
funs.add(() => i);
}
funs.forEach((f) => print(f())); // => 0 1 2
}
This would be (roughly) equivalent to:
main() {
var funs = [];
var i_outer;
for (i_outer = 0; i_outer < 3; i_outer++) {
i_inner := i_outer;
funs.add(() => i_inner);
}
funs.forEach((f) => print(f())); // => 0 1 2
}
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20171110.md#summary>
Summary
We are investigating ways to make the declaration of final locals easier.
In this proposal we suggest the use of := as new syntax to concisely
declare a fresh final local.
We also propose changes to the for and for-in statements to make the
declaration of final variables concise. The for loop would support the
:= syntax,
and a for-in statement without var or type would implicitly introduce a
fresh final variable.
--
For other discussions, see https://groups.google.com/a/dartlang.org/
For HOWTO questions, visit http://stackoverflow.com/tags/dart
To file a bug report or feature request, go to http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+***@dartlang.org.
For other discussions, see https://groups.google.com/a/dartlang.org/
For HOWTO questions, visit http://stackoverflow.com/tags/dart
To file a bug report or feature request, go to http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+***@dartlang.org.