Newsletter 2017-11-10.

Change-Id: Ibecaf1e42105093193897e2ee57ed24d34c5e7b8
Reviewed-on: https://dart-review.googlesource.com/20200
Reviewed-by: Florian Loitsch <floitsch@google.com>
This commit is contained in:
Florian Loitsch 2017-11-10 18:09:30 +00:00
parent 041f98ca8c
commit 145c9a5d56

299
docs/newsletter/20171110.md Normal file
View file

@ -0,0 +1,299 @@
# Dart Language and Library Newsletter
2017-11-10
@floitschG
Welcome to the Dart Language and Library Newsletter.
## Did You Know?
### 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.
``` dart
// Concise syntax for initializing fields while declaring parameters.
class A {
final int x;
A(this.x);
}
```
#### 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.
``` dart
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.
``` dart
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:
``` dart
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.
#### 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 `Completer` class.
``` dart
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).
#### 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:
``` dart
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.
#### 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:
``` dart
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:
``` dart
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:
``` dart
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);
}
```
## Shorter Final Variables
In Dart it is now easier to declare mutable locals, than to declare immutable variables:
``` dart
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.
``` dart
// 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).
``` dart
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.
### 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:
``` dart
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:
``` dart
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.
``` dart
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.
``` dart
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:
``` dart
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
}
```
### 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.