Discussion:
[dart-misc] Dart Language and Library Newsletter (2017-09-22)
'Florian Loitsch' via Dart Misc
2017-09-22 13:15:36 UTC
Permalink
Github link:
https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.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/20170922.md#did-you-know>Did
You Know?
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.md#literal-strings>Literal
Strings

Dart has multiple ways to write literal strings. This section shows each of
them and discusses when they are useful.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.md#quoted-strings>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.

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:

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.

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.

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.

// 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.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.md#escapes>
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.

var escaping = "One can escape with \"\\\".";print(escaping); // =>
One can escape with "\".

<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.md#raw-strings>Raw
Strings

A raw string is a string that ignores escape sequences. It is written by
prefixing the string literal with a 'r'.

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.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.md#multiline-strings>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:

var multi = """this stringspans multiple lines""";

To make it easier to align the contents of multiline strings, an immediate
newline after the starting delimiters is ignored:

var multi = """this stringspans 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.

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'""";

<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.md#concatenated-strings>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.

var interpolation = "interpolation";var combi = "a string "
'that goes over multiple lines " "and uses $interpolation "
r"and \raw strings"

<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.md#void-as-a-type>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.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.md#motivation>
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.

void bar() {}
main() {
var f = new Future.value(bar());
print(f.runtimeType); // => _Future<void>.

<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.md#history>
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:

// In Dart 1.xObject 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.

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.
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.md#the-void-type>
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.

<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.md#step-1>Step
1

We start by syntactically allowing void everywhere that other types are
allowed. Until now, Dart prohibited uses of voidexcept 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:

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:

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).
<https://github.com/dart-lang/sdk/blob/master/docs/newsletter/20170922.md#step-2>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:

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:

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.

class A<T extends void> { ... }// orclass 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 joinmethod 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 voidfunctions 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.
--
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.
Loading...