Dart Language and Library Newsletter

2017-08-04 @floitschG

Welcome to the Dart Language and Library Newsletter.

Under Active Development

This section provides updates to the areas we are actively working on. Many of these sections have a more detailed explanation in a previous newsletter.

Better Organization

Goal: collect and organize the documents (proposals, ...) the Dart language team produces.

We are storing all interesting proposals in docs/language/informal (in the Dart repository).

For example, there is a proposal for asserts in initializer lists assert_init. This feature allows to write basic assertions in the initializer list. The main use-case is for const constructors, which are not allowed to have bodies.

For example:

class A {
  final int y;
  const A(int x) : assert(x < 10), y = x + 1;
}

I will cover some of the proposals in future newsletters. Feel free to send me requests for specific proposals in that directory.

Void as a Type

Goal: allow void as a type. In particular, make it possible to use it in generics, such as Future<void>.

Erik (@eernstg) wrote a first proposal for an informal spec, and we are actively discussing his proposal now. I‘m in the process of writing an easily digestible document on this feature (hopefully for the next newsletter), but you can also read Erik’s (more formal) proposal here: https://gist.github.com/eernstg/7d79ef7f3e56daf5ce1b7e57684b18c6

We hope to add this proposal to our documentation directory, soon.

Updates to the Core Libraries

Goal: clean up the core libraries. Deprecate rarely used classes/methods and add missing functionality.

Small progress: we wrote CLs (but haven't submitted them yet) for deprecating SplayTreeMap, SplayTreeSet and DoubleLinkedList. These classes (and their tests) are migrated to package:collection. The core SDK has been updated to not use any of these classes anymore.

While we haven't finalized the list of core library changes, here are a few more likely candidates:

  • add map to Map: Iterable<T> map<T>(T Function(K, V) f). This function makes it possible to iterate over all key-value pairs (similar to Map.forEach) and build a new iterable out of them.
  • add followedBy to Iterable: Iterable<T> followedBy(Iterable<T> other). With this method, it is finally possible to concatenate two iterables. This makes the unintuitive [iterable1, iterable2].expand((x) => x) pattern obsolete: iterable1.followedBy(iterable2).
  • add + to List: concatenates two lists: List<T> operator +(List<T> other). Contrary to followedBy (which is inherited from Iterable), using + to concatenate two lists produces a list that is eagerly constructed. Example: [1, 2] + [3, 4] // -> [1, 2, 3, 4].

These changes are more breaking than the ones we mentioned last week. We don't know yet how and when we want to land them to avoid the least amount of disruption.

Deferred Loading

The current spec does not allow types to be used across deferred library imports. This makes sense, when the deferred sources are not known. This means that users currently have to introduce interfaces for classes that could just be referenced directly.

// lib1.dart
abstract class InterfaceA {
  foo();
}
// lib2.dart
import 'lib1.dart';
class A implements InterfaceA {
  foo() => 499;
}
// lib3.dart
import 'lib1.dart';
import 'lib2.dart' deferred as def;

main() {
  InterfaceA a;
  def.loadLibrary().then((_) => a = new def.A());
}

Since dart2js and DDC actually know all the sources during compilation, this boilerplate code feels painful. We are thus looking into alternatives.

We discussed one proposal that suggested to remove deferred loading from the language entirely and leave it up to the compilers (DDC and dart2js primarily) to deal with deferred loading. Instead of language syntax, the compilers would have used annotations and specially recognized functions to effectively have the same semantics as the current specification. They could then evolve from there to provide a better experience.

Here is an example, of how this would look like:

import "package:deferred/deferred.dart" show deferred, loadLibrary;

@Deferred("lib1")
import "lib1.dart" as lib1;

void main() {
  // loadLibrary is specially recognized by dart2js.
  loadLibrary(const Deferred("lib1")).then((_) {
    /* Assume lib1 has been loaded. */
    print(new lib1.A());
  });
}

Since the language doesn't provide any guidance, DDC and dart2js could then implement their own restrictions (potentially using more annotations).

We have discarded this idea, because (among other reasons) the language front-end is too much involved in deferred loading. For example, it needs to keep track which types are referenced through (deferred) prefixes. If the front-end needs to be involved in such a high degree, then we should specify what exactly it needs to do.

Our plan is now to allow uses of deferred types even when the deferred libraries haven't been loaded yet. This means that sources of deferred libraries must be available during compilation of non-deferred functions:

/// lib1.dart
class A {}

/// lib2.dart
export "lib1.dart" show A;

/// main.dart
import "lib1.dart";
import "lib2.dart" deferred as def;

main() {
  print(new A() is def.A);  // Requires knowledge of `def.A`.
}

While this change is clearly breaking (and we know of a few programs that created the deferred libraries in the main library), in practice, deferred loading is mainly used as a deployment tool to cut down the size of the initial payload. At that time all sources are available.

The main tool that is affected by the new semantics is dart2js. We are tracking work on this feature in dart2js here: https://github.com/dart-lang/sdk/issues/29903

Once dart2js has the resources to implement and experiment with the new semantics we will provide updates.