diff --git a/docs/newsletter/20170908.md b/docs/newsletter/20170908.md new file mode 100644 index 00000000000..702eb4e8c42 --- /dev/null +++ b/docs/newsletter/20170908.md @@ -0,0 +1,145 @@ +# Dart Language and Library Newsletter +2017-09-08 +@floitschG + +Welcome to the Dart Language and Library Newsletter. + +## Follow-Up - Call +Last newsletter we announced our desire to remove the `call` operator from the language. We got some feedback that showed some uses in the wild. Please keep them coming. It will definitely influence our decision whether (or when) we are going to remove the operator. + +We also forgot an additional benefit of removing the operator: since users wouldn't be able to implement functions by themselves we could extend the `Function` interface with useful properties. For example, we could add getters that return the arity / signature of the receiver closure: + +``` dart +void onError(Function errorHandler) { + // positionalArgumentCount includes optional positional parameters. + if (errorHandler.positionalParameterCount >= 2) { + errorHandler(error, stackTrace); + } else { + errorHandler.positionalParameterCount == 1); + errorHandler(error); + } +} +``` + +## Fuzzy Arrow +In Dart 1.x `dynamic` was used for both the *top* and *bottom* of the typing hierarchy. Depending on the context, `dynamic` could either mean `Object` (top) or `Null` (bottom). For the remainder of the section remember that every type is a subtype of `Object` (which is why it's called "top"), and every type is a supertype of `Null`. + +This schizophrenic interpretation of `dynamic` can be observed easily with generic types: + +``` dart +void main() { + print([1, 2, 3] is List); // Use `dynamic` as bottom. => true. + print([1, 2, 3] is List); // Use `dynamic` as top. => true. +} +``` +In the first statement, `List` is used as a supertype of `List`, whereas in the second statement, `List` is used as subtype of `List`. This works for every type and not just `int`. As such, `dynamic` clearly is top and bottom at the same time. + +With strong mode, this dual-view of `dynamic` became an issue, and, for the sake of soundness, `dynamic` was downgraded to `Object`. It still supports dynamic calls, but can't be used as bottom anymore. In strong mode, the second statement thus prints "false". However, strong mode kept one small exception: *fuzzy arrows*. + +The fuzzy arrow exception allows `dynamic` to be used as if it was `bottom` when it is used in function types. Take the following example: +``` dart +/// Fills [list2] with the result of applying [f] to every element of +/// [list1] (if [f] is of arity 1), or of applying [f] to every +/// element of [list1] and [list2] (otherwise). +void map1or2(Function f, List list1, List list2) { + for (int i = 0; i < list1.length; i++) { + var x = list1[i]; + if (f is Function(dynamic)) { + list2[i] = f(x); + } else { + var y = list2[i]; + list2[i] = f(x, y); + } + } +} + +int square(int x) => x * x; + +void main() { + var list1 = [1, 2, 3]; + var list2 = new List(3); + map1or2(square, list1, list2); + print(list2); +} +``` +This code is relatively dynamic and avoids lots of types (and in particular generic arguments to `map1or2`), but it is a correct strong mode program. In DDC it prints `[1, 4, 9]`. + +There are some implicit `dynamic`s in the program, but we are really interested in the one explicit `dynamic` in the function-type test: `if (f is Function(dynamic))`. Intuitively, that test looks good: we don't mind which type the function takes and thus wrote `dynamic` for the parameter type. However, that wouldn't work if `dynamic` was interpreted as `Object`. In that case, the `is` check asks whether the provided `f` could be invoked with *any* `Object`. That's not what we want. We don't want to invoke it with a random object value that we found somewhere, but invoke it with the values from `list1`. It's the caller's responsibility to make sure that the types match. In fact, we don't care for the type at all. The `is` check is just here to test for the arity. + +Since checking for arity is a common pattern, strong mode still treats `dynamic` as bottom in this context. This function types is thus equivalent to an arity check. + +For a long time, the fuzzy arrow exception was necessary. Dart didn't have any other way to do arity checking. Only with the move of the `Null` type to the bottom of the typing hierarchy, was it possible to explicitly use the bottom type instead of just dynamic. A sounder way of asking for a function's arity is thus: + +``` dart +if (f is Function(Null)) { +``` + +This can be read as: "is `f` a function that takes at least `null`?". Without non-nullable types *every* 1-arity function takes `null` and this test is equivalent to asking whether the function takes one argument. + +`` +With non-nullable types, the bottom type would need to change, since there are types that wouldn't accept `null` anymore. At that point we would need to introduce a `Nothing` type, and the `is`-check would need to be rewritten to `if (f is Function(Nothing))`. Admittedly, the spoken interpretation doesn't sound as logical anymore: "is `f` a function that takes at least Nothing?" +`` + +Since there is now a "correct" way of testing for the arity of functions, the language team recently started to investigate whether we could drop the fuzzy arrow exception from strong mode (and thus Dart 2.0). + +Although the removal of fuzzy errors leads to breakages, our experience is pretty positive so far. The biggest problems arise in cases where the current type system is too weak to provide a correct replacement. Among those, `Map.fromIterables` clearly makes the biggest problems. The old signature of that constructor is `Map.fromIterable(Iterable iterable, {K key(element), V value(element)})`. Implicitly, both functions for `key` and `value` take `dynamic` arguments and use the fuzzy arrow exceptions to support iterables of any kind. + +Without the fuzzy arrow exception the implicit `dynamic` in those types is read similar to `Object`, thus requiring users to provide functions that can deal with *any* object (and not just the ones from the `iterable`). + +Unfortunately, our trick of replacing the `dynamic` with `Null` doesn't work here: + +``` dart +Map.fromIterable(Iterable iterable, + {K key(Null element), V value(Null element)}) { + ... +} + +// Works when the argument is not a function literal: +new Map.fromIterable(["1", "2"], keys: int.parse); + +// Doesn't work, with function literal: +new Map.fromIterable([1, 2], values: (x) => x.toString()); +``` + +The reason the second instantiation doesn't work is that Dart uses the context of a function literal to infer the parameter type. In this case the literal `(x) => x.toString()` is used in a context where a `V Function(Null)` is expected, and the literal is thus automatically adapted to satisfy this signature: `String Function(Null)`. However, that means that any invocation of this function with a value that is not `null` yields to a dynamic error. + +The correct way to fix this constructor is to allow generic arguments for constructors: + +``` dart +Map.fromIterable(Iterable iterable, + {K key(T element), V value(T element)}) { + ... +} +``` +Supporting generic arguments for constructors is on our roadmap, but will not make it for Dart 2.0. In the meantime we either have to live with requiring functions that take objects, or we will have to change the `key` and `value` type annotation to `Function`, thus losing the arity and type information: + +``` dart +Map.fromIterable(Iterable iterable, {Function key, Function value}) { + ... +} +``` + +## Enhanced Type Promotion +As mentioned in a previous newsletter: one of our goals is to improve Dart's type promotion. We want to make better use of `is` and `is!` checks. For example, promote `x` to `int` after the `if` in the following code: `if (x is! int) throw x;`. + +When the language team discussed this topic we looked at the conditions under which type promotion would be useful and intuitive. One of the current restrictions is that promoted variables may not be assigned again: + +``` dart +void foo(Object x) { + if (x is String) { + x = x.subString(1); // Error: subString is not defined for Object. + print(x + "suffix"); + } +} + +void bar(Object x) { + if (x is WrappedInt) { + x = x.value; // Error: `value` is not defined for Object. + } + assert(x is int); +} +``` + +As can be seen in these two examples, assignments would require an analysis that deals with flow-control, and that assigns potentially different types to the same variable. Inside `foo` the user wants to continue using `x` as `String`, whereas in `bar` the user wants to use `x` as an `Object` after the assignment. + +We have discussed multiple approaches to provide the correct, intuitive behavior in these cases, and we are confident that we can provide a solution that will work in most cases. However, we don't want to delay or block the "easy" improvements, and therefore decided to exclude assignments from the current proposal. We will come back to assignments of promoted variables in the future.