For the latest Dart news, visit our new blog at https://medium.com/dartlang .
At the last TC52 meeting on Feb 24, 2015, we discussed a number of additional language features for Dart - including async, tear-offs and null-aware operators.
The virtual machine now implements the sync* mechanism. The async* mechanism is coming along. The dart2js implementation is complete with respect to the feature set, but it still needs to be debugged.
The development of the async features has given rise to some useful insights. For instance, sync* creates a synchronous generator, handling all the boilerplate for lazy iteration. However, the documentation of the library classes need an enhanced level of precision in order for this to work well with the language implementation and specification. The connection arises because the language specification says that Iterable is implemented by sync* methods. A conflict arises because Iterator and other classes are allowed to cache some values such as the length, but this is not applicable for sync* functions, and so the spec needs to restrict that. Consequently, the library documentation has been revised.
An issue arises with “nested” empty iterables for yield*. The idea is that yield* is used to merge the output from a subiterator into the output of a given iterator. The wording in the language specification did not specify exactly what to do if that subiterator were empty, but the intention is that the subiterator should be processed normally and iteration should be continued from where it was before that subiterator was inserted. Adjustments to the specification will be made to clarify this.
Several issues have come up in connection with async primitives:
One issue is whether a construct on the form await (throw x) can throw immediately, or “await always awaits” must prevail.
It was noted that the return type of async uses flatten to avoid nested future types, and also that this should not create any difficulties for the type analysis.
It is not obvious whether await-for loops should require a Stream as the source of the awaited data, or they should allow for non-Stream objects with the required features. The question arises statically, where a type warning may or may not be given for a non-Stream. The question arises dynamically where a non-Stream may cause a runtime type error.
Currently, the implementation performs such a check dynamically, even in production mode. We quickly agreed that it is problematic to have a dynamic check without an accompanying static warning, and also that any such dynamic check should probably only be performed in checked mode. It is not obvious how to resolve the overall issue, though.
Erik pointed out that there is an underlying principle for this kind of decision: The language may allow for any object that responds to the requests made by the generated code, or it may enforce a nominal type such that only objects implementing Stream can be used. The rationale for the former approach (let us call it the “pragmatic” approach) is to run everything that can plausibly be given a semantics, no matter whether or not it is aligned with the associated type structure. The rationale for the latter approach (let us call it the “typeful” approach) is to support software correctness via explicit contractual obligations and expectations (as expressed by nominal types and supported by dartdoc comments etc.). It is possible to use both a pragmatic approach and a typeful approach, both statically and dynamically, so all four combinations must be considered.
Dynamically, the pragmatic approach seems to be well aligned with many other decisions in the language design (it’s the “Darty” choice); but as Gilad mentioned, the code that actually implements the features which are used in this context is so subtle that ad-hoc alternative implementations to the ones in the standard library are very likely to be a steady source of stubborn bugs. For the few highly sophisticated developers who insist on writing their own implementations of “something that works as a stream” it should be easy to add an implements Stream clause to their implementation, such that it works with the stricter, typeful approach.
Statically, the pragmatic approach may be seen as a mistake, especially in the (presumably, vast majority of) cases where a non-Stream is used by accident rather than by intention. Conversely, if a static warning is given whenever a non-Stream is used in this context, it will cause frustration for the sophisticated developers who have carefully chosen to use a non-Stream.
In summary, we may have implicitly preferred the typeful approach statically and the pragmatic approach dynamically, but no explicit decision was made.
Another matter is that a decision on an underlying principle in this case naturally affects the treatment of similar constructs, including the standard for statement, where changes would obviously be much more difficult.
A quite different issue is the decision about whether yield in async* may suspend. If a tight loop contains a yield, it may produce a large number of data items which are not being received quickly enough. These situations create a need for more flexibility than that which is currently granted by the specification.
Finally, Gilad mentioned that there are some missing static warnings in relation to async*/sync*, and a few other small issues.
TC52 has a royalty-free patent policy. Therefore, when a proposal is accepted by the DEP committee for processing by TC52, the original contributor of the DEP needs to sign the 'ECMA TC52 external contributor form'.
For generalized tear-offs, the coverage and discussions at the previous meeting are still valid. Now there is a complete proposal which is currently getting prepared for markdown presentation etc. It can be found at https://github.com/gbracha/generalizedTearOffs. It is done in a way that should be identical to the way that an external contributor could submit a proposal, such that it also serves as a test of the procedure and the associated documents.
For null-aware operators, the information and discussions from the previous meeting are still valid. In this case it is a very simple proposal, just syntactic sugar, but it comes at a low cost.
We briefly discussed a proposal from Erik to change the class Function to take one type argument which would be the return type of the function represented by the given instance. This could enable the type Function<T> to be more useful than the current type Function, and it seems to be a benign change because all existing occurrences of Function would be reinterpreted to mean Function<dynamic>, which would have a backward compatible semantics.
We did not discuss configurable imports, union types, generic methods, closure tear-offs, improvements to Type Promotion, nor subtyping for function types. In most cases this was because there is nothing new. For function type subtyping it was because we need to consider changes to the runtime semantics of Dart very, very carefully before we commit to them.
Update on async
Gilad reported that the implementation had, unsurprisingly, exposed some subtleties in the spec that need to be considered. Some small adjustments in the language specification may follow from this.The virtual machine now implements the sync* mechanism. The async* mechanism is coming along. The dart2js implementation is complete with respect to the feature set, but it still needs to be debugged.
The development of the async features has given rise to some useful insights. For instance, sync* creates a synchronous generator, handling all the boilerplate for lazy iteration. However, the documentation of the library classes need an enhanced level of precision in order for this to work well with the language implementation and specification. The connection arises because the language specification says that Iterable is implemented by sync* methods. A conflict arises because Iterator and other classes are allowed to cache some values such as the length, but this is not applicable for sync* functions, and so the spec needs to restrict that. Consequently, the library documentation has been revised.
An issue arises with “nested” empty iterables for yield*. The idea is that yield* is used to merge the output from a subiterator into the output of a given iterator. The wording in the language specification did not specify exactly what to do if that subiterator were empty, but the intention is that the subiterator should be processed normally and iteration should be continued from where it was before that subiterator was inserted. Adjustments to the specification will be made to clarify this.
Several issues have come up in connection with async primitives:
One issue is whether a construct on the form await (throw x) can throw immediately, or “await always awaits” must prevail.
It was noted that the return type of async uses flatten to avoid nested future types, and also that this should not create any difficulties for the type analysis.
It is not obvious whether await-for loops should require a Stream as the source of the awaited data, or they should allow for non-Stream objects with the required features. The question arises statically, where a type warning may or may not be given for a non-Stream. The question arises dynamically where a non-Stream may cause a runtime type error.
Currently, the implementation performs such a check dynamically, even in production mode. We quickly agreed that it is problematic to have a dynamic check without an accompanying static warning, and also that any such dynamic check should probably only be performed in checked mode. It is not obvious how to resolve the overall issue, though.
Erik pointed out that there is an underlying principle for this kind of decision: The language may allow for any object that responds to the requests made by the generated code, or it may enforce a nominal type such that only objects implementing Stream can be used. The rationale for the former approach (let us call it the “pragmatic” approach) is to run everything that can plausibly be given a semantics, no matter whether or not it is aligned with the associated type structure. The rationale for the latter approach (let us call it the “typeful” approach) is to support software correctness via explicit contractual obligations and expectations (as expressed by nominal types and supported by dartdoc comments etc.). It is possible to use both a pragmatic approach and a typeful approach, both statically and dynamically, so all four combinations must be considered.
Dynamically, the pragmatic approach seems to be well aligned with many other decisions in the language design (it’s the “Darty” choice); but as Gilad mentioned, the code that actually implements the features which are used in this context is so subtle that ad-hoc alternative implementations to the ones in the standard library are very likely to be a steady source of stubborn bugs. For the few highly sophisticated developers who insist on writing their own implementations of “something that works as a stream” it should be easy to add an implements Stream clause to their implementation, such that it works with the stricter, typeful approach.
Statically, the pragmatic approach may be seen as a mistake, especially in the (presumably, vast majority of) cases where a non-Stream is used by accident rather than by intention. Conversely, if a static warning is given whenever a non-Stream is used in this context, it will cause frustration for the sophisticated developers who have carefully chosen to use a non-Stream.
In summary, we may have implicitly preferred the typeful approach statically and the pragmatic approach dynamically, but no explicit decision was made.
Another matter is that a decision on an underlying principle in this case naturally affects the treatment of similar constructs, including the standard for statement, where changes would obviously be much more difficult.
A quite different issue is the decision about whether yield in async* may suspend. If a tight loop contains a yield, it may produce a large number of data items which are not being received quickly enough. These situations create a need for more flexibility than that which is currently granted by the specification.
Finally, Gilad mentioned that there are some missing static warnings in relation to async*/sync*, and a few other small issues.
Update on “Dart Enhancement Proposal”
We are ready to launch the process, and several activities are already taking place. The process is github-based (see https://github.com/dart-lang/dart_enhancement_proposals). The idea is that DEP authors file an issue in this github repository which includes a link to the proposal itself, which is stored as a separate github repository. The proposal repository may also contain other elements such as examples, implementation etc. The committee will go over incoming proposals according to the flowchart.TC52 has a royalty-free patent policy. Therefore, when a proposal is accepted by the DEP committee for processing by TC52, the original contributor of the DEP needs to sign the 'ECMA TC52 external contributor form'.
Update on language extensions
Two things are likely to be ready for the next version of the language specification: Generalized tear-offs, and null-aware operators.For generalized tear-offs, the coverage and discussions at the previous meeting are still valid. Now there is a complete proposal which is currently getting prepared for markdown presentation etc. It can be found at https://github.com/gbracha/generalizedTearOffs. It is done in a way that should be identical to the way that an external contributor could submit a proposal, such that it also serves as a test of the procedure and the associated documents.
For null-aware operators, the information and discussions from the previous meeting are still valid. In this case it is a very simple proposal, just syntactic sugar, but it comes at a low cost.
We briefly discussed a proposal from Erik to change the class Function to take one type argument which would be the return type of the function represented by the given instance. This could enable the type Function<T> to be more useful than the current type Function, and it seems to be a benign change because all existing occurrences of Function would be reinterpreted to mean Function<dynamic>, which would have a backward compatible semantics.
We did not discuss configurable imports, union types, generic methods, closure tear-offs, improvements to Type Promotion, nor subtyping for function types. In most cases this was because there is nothing new. For function type subtyping it was because we need to consider changes to the runtime semantics of Dart very, very carefully before we commit to them.