dart-sdk/docs/newsletter/20170922.md

294 lines
16 KiB
Markdown
Raw Normal View History

# Dart Language and Library Newsletter
2017-09-22
@floitschG
Welcome to the Dart Language and Library Newsletter.
# Did You Know?
## Literal Strings
Dart has multiple ways to write literal strings. This section shows each of them and discusses when they are useful.
### Quoted Strings
For the lack of a better word I call the standard strings "quoted strings". These are normal strings delimited by single or double quotes. There is no difference between those two delimiters.
``` dart
var x = "this is a string";
var y = "this is another string";
```
Quoted strings can contain escape sequences (see below) and string-interpolations. A dollar followed by an identifier inserts the `toString()` of the referenced value:
``` dart
var hello = "Hello";
var readers = "readers";
print("$hello $readers");
```
It is common to have no space between two spliced values. For this reason, the specification actually doesn't allow any identifier after the dollar, but requires the identifier to be without any dollar.
``` dart
var str1 = "without";
var str2 = "space";
print("$str1$str2"); // => withoutspace.
var dollar$variable = "needs more work";
print("$dollar$variable"); // Error. This won't work.
```
For identifiers that contain dollars, or for more complex expressions, one can use curly braces to delimit the expression that should be spliced in.
``` dart
var dollar$variable = "now works";
print("${dollar$variable}"); // => now works.
var x = 1;
var y = 2;
print("${x + y}"); // => 3.
```
Quoted strings are single line strings that may not contain a verbatim newline character.
``` dart
// ERROR: missing closing ".
var notOk = "Not possible
to write strings over
multiple lines.";
```
This is a safety feature: if all strings can contain newlines, it's too easy for a missing end-quote to trip up the user and the compiler. In the worst case the program might even still be valid but just do something completely different.
```
var someString = 'some string";
var secondString = 'another one";
```
Dart has requires developers to *opt-in* to multiline strings (see below), or to use escape sequences to insert newline characters without actually breaking the code line.
#### Escapes
Dart features the following common escape sequences:
* `\n`: a newline. (0x0A in the ASCII table).
* `\r`: carriage return (0x0D).
* `\f`: form feed (0x0C).
* `\b`: backspace (0x08).
* `\t`: tab (0x09).
* `\v`: vertical tab (0x0B).
* `\x D0 D1`, where `D0` and `D1` are hex digits: the codeunit designated by the hexadecimal value.
For Unicode Dart furthermore supports the following sequences:
* `\u{hex-digits}: the unicode scalar value represented by the given hexadecimal value.
* `\u D0 D1 D2 D3` where `D0` to `D3` are hex digits: equivalent to `\u{D0 D1 D2 D3}`.
Every other `\` escape represents the escaped character. It can removed in all cases, except for `\\` and the delimiters of the string.
``` dart
var escaping = "One can escape with \"\\\".";
print(escaping); // => One can escape with "\".
```
### Raw Strings
A raw string is a string that ignores escape sequences. It is written by prefixing the string literal with a `'r'`.
``` dart
var raw = r'\no\escape\';
print(raw); // => \no\escape\
```
Raw strings don't feature string interpolations either. In fact, a raw string is a convenient way to write the dollar string: `r"$"`.
Raw strings are particularly useful for regular expressions.
### Multiline Strings
A string that is delimited by three single or double quotes is a multiline string. Unlike to the other strings, it may span multiple lines:
``` dart
var multi = """this string
spans multiple lines""";
```
To make it easier to align the contents of multiline strings, an immediate newline after the starting delimiters is ignored:
``` dart
var multi = """
this string
spans multiple lines""";
print(multi.startsWith("t")); // => true.
```
Contrary to Ceylon (https://ceylon-lang.org/documentation/1.3/reference/literal/string/) Dart does not remove leading indentation. In fact, Dart currently has no builtin way to remove indentation of multiline strings at all. We plan to add this functionality in the future: https://github.com/dart-lang/sdk/issues/30864.
Multiline strings can contain escape sequences and string interpolations. They can also be raw.
``` dart
var interpolation = "Interpolation";
var multiEscapeInter = """
$interpolation works.
escape sequences as well: \u{1F44D}""";
var raw = r"""
a raw string doesn't
use $ for interpolation.
""";
```
Multiline strings are not just useful for strings that span multiple lines, but also when both kind of quotes happen to be in the text. This is especially the case for raw strings since they can't escape the delimiters.
```
var singleLine = '''The book's title was "in quotes"''';
var rawSingleLine = r"""contains "everything" $and and the 'kitchen \sink'""";
```
### Concatenated Strings
Whenever two string literals are written next to each other, they are concatenated. This is most often used when a string exceeds the recommended line length, or when different parts of a string literal are more easy to write with different literals.
``` dart
var interpolation = "interpolation";
var combi = "a string "
'that goes over multiple lines '
"and uses $interpolation "
r"and \raw strings"
```
# Void as a Type
As mentioned in earlier newsletters, we want to make `void` more useful in Dart. In Dart 1.24 we made the first small improvements, but the big one is to allow `void` as a type annotation and generic type argument (as in `Future<void>`).
The following text should be considered informative and sometimes ignores minor nuances when it simplifies the discussion.
## Motivation
Dart currently allows `void` only as a return type. This covers the most common use-case, but, more and more, we find that `void` would be very useful in other places. In particular, in asynchronous programming and as the result of a type-inference, allowing `void` in more locations would make a lot of code simpler. In both cases, a return type directly feeds into a generic type.
In asynchronous programming, most often with `async`/`await`, the result of a computation is wrapped in a `Future`. When the computation has a return-type `void`, then we would like to see `void` as the generic type of `Future`. Since that's not yet allowed, many developers use `Null`, which comes with other problems (already highlighted in previous newsletters).
The type inference already uses the return types of methods to infer generic arguments to functions. The `void` type, as a generic argument, thus already exists in strong mode. Users just don't have a way to write it.
``` dart
void bar() {}
main() {
var f = new Future.value(bar());
print(f.runtimeType); // => _Future<void>.
```
## History
Historically, `void` was (and still is) very restricted in Dart. The Dart 1.x specification makes `void` a reserved word that can only appear as a return type for functions. This means that implementations don't ever need to reify the `void` type itself, and, indeed, the dart2js implementation simply implemented `void` as a boolean bit on function types.
Conceptually, `void` serves as a way to distinguish procedures (no return value) and functions (return value). The `void` type is not part of the type hierarchy as can best be seen when looking at Dart's function-type rules. Dart 1.x has *very* relaxed function-subtyping rules, that is (roughly speaking) bivariant on the return type and the parameter types. For example, `Object Function(int)` is a subtype of `int Function(Object)`. Despite these liberties, a `void Function()` is not an `int Function()`.
Example:
``` dart
// In Dart 1.x
Object foo(int x) => x;
void bar() {}
main() {
print(foo is int Function(Object)); // true
print(bar is Object Function()); // false
}
```
At the same time, `void` functions are not really procedures, either. Like every function, `void` functions do return a value (usually `null`), and void methods can be overridden with non-`void` methods.
``` dart
class A {
void foo() {};
void bar() => foo();
}
class B extends A {
int foo() => 499;
}
```
Given these properties, a better interpretation of `void` is to think of them as values that simply shouldn't be used. Having the return type `void` is another way of saying: "I return something of type `Object`, but the value is useless and should not be relied upon". It doesn't say anything about subclasses which might have a more interesting value and are free to change `void` to something else.
With this interpretation, making `void` a full-fledged type becomes straight-forward: treat `void` similarly to `Object` (subtype-wise), but disallow using values of *static* type `void`.
## The `void` Type
Now that the meaning of `void` is clear, it's just a matter of adding it to the language.
Over long discussions we came up with a plan to land this feature in two steps:
1. generalize the `void` type and disallow obvious misuses,
2. add more checks to disallow less obvious accesses to `void`.
### Step 1
We start by syntactically allowing `void` everywhere that other types are allowed. Until now, Dart prohibited uses of `void` except as return types of functions.
When `void` is used as a type, the subtyping rules treat it similar to `Object`. This means that `Future<void>` can be used everywhere, where `Future<Object>` is allowed. The only exception is function types, where the current restrictions are kept: a `void Function()` is still not an `Object Function()`.
A simple restriction disallows obvious misuses of `void`: expressions of static type `void` are only allowed in specific productions. Among many other locations, they are *not* allowed as right-hand sides of assignments or as arguments to function calls:
``` dart
int foo(void x) { // `foo` receives a `void`-typed argument.
var y = x; // Disallowed.
bar(x); // Disallowed.
}
Future<void> f = new Future(() { /* do something. */ });
f.then((x) { // Type inference infers `x` of type `void`.
print(x); // Disallowed.
});
var list = Future.await([f]); // `f` from above. `list` is of type `List<void>`.
print(list[0]); // Disallowed.
```
Together with a type-inference that recognizes (and infers) `void`, these changes make `void` a full type in Dart. As discussed in the next section there are static checks that we want to add, but even in this form, `void` brings lots of benefits.
A common pattern is to use `Null` for generic types that would have been `void` if it was allowed. This might seem intuitive, since `void` functions effectively return `null` when there is no `return` statement, but it also prevents us from providing users with some useful warnings. `Future<Null>` is a subtype of *every* other `Future`. There is no static or dynamic error when a `Future<Null>` is assigned to `Future<int>`. With `Future<void>` there would be a dynamic error when it is assigned to `Future<int>`.
Furthermore, using a `void` value (more concretely an expression of static type `void`) would be forbidden in most syntactic locations. Examples:
``` dart
voidFuture.then((x) { ... use(x) ... }); // x is inferred as `void`, and the use is an error.
var x = await voidFuture; // Error.
var y = print("foo"); // Error. void expression in RHS of an assignment.
foo(void x) {}
// Note that the check is syntactic and doesn't look at the target type:
foo(print("bar")); // Error. void expression in argument position.
```
These errors are only checked statically (since, at runtime, no value can be of type `void`).
### Step 2
In step 1 we are restricting obvious uses of `void`. There remain many holes how a `void` value can leak. The easiest is to use the `void`-`Object` equivalence when `void` is used as a generic type:
``` dart
Future<void> f1 = new Future(() { /* do something */ });
Future<Object> f2 = f1; // Legal, since Object is treated like void.
f2.then(print); // Uses the value.
```
In step 2 these non-obvious `void` uses are clamped down. It's important to note that none of our proposals makes it impossible to leak `void`. _Voidness preservation_ is intended to provide reasonable warnings and errors while not getting too much in the way.
Take, for example, the following class that uses generic types:
``` dart
class A<T> {
final T x;
A(T Function() f) : this.x = f();
toString() => "A($x)";
}
```
Since it uses `x.toString()` in the `toString` method, it uses `x` as an `Object`. That means that `new A(voidFun).toString()` ends up using `x` which was marked as `void`.
In the remainder of this document we use the term "`void` value" to refer to variables like `A.x`. That is, values that users should not access, because they are or were typed as `void` (directly or indirectly).
One could easily argue that this is a limitation of the type system. Either `A` should opt into supporting `T` being equal to `void`, or, inversely, it should require `T` to extend `Object`.
``` dart
class A<T extends void> { ... }
// or
class A<T extends Object> { ... }
```
In practice, this would be too painful. A surprising number of classes actually need to support `void` values. For example `List<void>` is very useful for `Future.wait` which may take a `List<Future<void>>` and returns the result of the futures.
At the same time, `List` also demonstrates that restricting access to its generic values gets in the way. `List` has a `join` method that combines the strings of all entries, by invoking `toString()` on all of its entries. This means, that the generic type of `List` must both support `void` and yet be usable as `Object`.
For all these reasons, Dart doesn't guarantee that `void` values can never be accessed. In step 2, Dart just gets *static* rules that disallow assignments that obviously leak `void` values and that could be avoided. The easiest example is `List<Object> x = List<void>`. A rule will forbid such assignments. This fits the developers intentions: "a list of values that shouldn't be used, can't be assigned to a list that wants to use the values".
The exact rules for voidness preservations are complicated and haven't been finalized yet. They are surprisingly difficult to specify, especially because we want to match them with user's intuitions. We will add them at a later point. Initially, they will be warnings, and eventually, we will make them part of the language and make them errors.
It makes sense to have step 1 separately, even though it is only checking direct usage of void values: The execution of a Dart program will proceed just fine even in the case where a "void value" is passed on and used. It is going to be a regular Dart object, no fundamental invariants of the runtime are violated, and it is purely an expression of programmer _intent_ that this particular value should be ignored. This makes it possible to get started using `void` to indicate that certain values should be ignored when used in simple and direct ways. Later, with step 2 checks in place, some indirect usages can be flagged as well.
Note that we do not intend to ever add runtime checking of voidness preservation, `void` will always work like `Object` at runtime; at least, once `void` function return types aren't treated specially anymore. As mentioned earlier, Dart treats `void` functions as separate from non-`void` functions (the "procedure" / "function" distinction). Once voidness preservation rules are in place, we intent to remove that special treatment. Static checks will prevent the use of a `void` function in a context where a non-`void` function is expected. This means that the static checks will catch almost all misuses of `void` functions. The special handling of functions in the dynamic typing system would then become unnecessary.