| # Dart Language and Library Newsletter |
| 2017-08-18 |
| @floitschG |
| |
| Welcome to the Dart Language and Library Newsletter. |
| |
| ## If you missed it |
| Did you know that you can write trailing commas to arguments and parameters? This feature was added to the specification about a year ago. |
| |
| It's main use-case is to unify parameter and argument lists that span multiple lines. For example, [Flutter] uses it extensively to keep tree-like instantiations nicely aligned: |
| |
| [Flutter]: http://flutter.io |
| |
| ``` dart |
| return new Material( |
| // Column is a vertical, linear layout. |
| child: new Column( |
| children: <Widget>[ |
| new MyAppBar( |
| title: new Text( |
| 'Example title', |
| style: Theme.of(context).primaryTextTheme.title, |
| ), |
| ), |
| new Expanded( |
| child: new Center( |
| child: new Text('Hello, world!'), |
| ), |
| ), |
| ], |
| ), |
| ``` |
| |
| Note how every argument list ends with a comma. The `dartfmt` tool knows about these trailing commas and ensures that the individual entries stay on their own lines so that it is easy to move them around with cut and paste. |
| |
| Recently, we also updated the specification to allow trailing commas in `assert`s. This makes the syntax of `assert`s more consistent with function calls. |
| |
| ### Function Type Syntax |
| A few months ago, we added a new function type syntax to Dart (we mentioned it in our first newsletter). |
| |
| ``` dart |
| // Examples: |
| typedef F = void Function(int); // A void function that takes an int. |
| |
| void foo(T Function<T>(T x) f) { // foo takes a generic function. |
| ... |
| } |
| |
| class A { |
| // A has a field `f` of type function that takes a String and returns void. |
| void Function(String) f; |
| } |
| ``` |
| |
| Before we added the new function-type syntaxes, we evaluated multiple options. In this section I will summarize some of the discussions we had. |
| |
| #### Motivation |
| The new function-type syntax intends to solve three issues: |
| |
| 1. the old `typedef` syntax doesn't support generic types. |
| 2. the old function syntax can't be used for fields and locals. |
| 3. in the old syntax, providing only one identifier in an argument position is interpreted as name and not type. For example: `typedef f(int);` is *not* a typedef for a function that expects an `int`, but for a function that expects `dynamic` and names the argument "int". |
| |
| With Dart 2.0 we will support generic methods, and also generic closures. This means that a function can accept a generic function as argument. We were lacking a way to express this type. |
| |
| Dart 1.x has two ways to express function types: a) an inline syntax for parameters and b) `typedef`s. |
| |
| It was easy to extend the inline syntax to support generic arguments: |
| |
| |
| ``` dart |
| // Takes a function `factoryForA` that is generic on T. |
| void foo(A<T> factoryForA<T>()) { |
| A<int> x = factoryForA<int>(); |
| A<String> x = factoryForA<String>(); |
| } |
| ``` |
| |
| However, there was no easy way to do the same for `typedef`s: |
| |
| ``` dart |
| typedef A<T> FactoryForA<T>();// Does *not* do what we want it to do: |
| |
| FactoryForA f; // A function that returns an `A<dynamic>`. |
| FactoryForA<String> f2; // A function that returns an `A<String>`. |
| f<int>(); // Error: `f` is not generic. |
| ``` |
| |
| We had already used the most consistent place for the generic method argument as a template argument to the `typedef` itself. If we could go back in time, we could change it as follows: |
| |
| ``` dart |
| typedef<T> List<T> TemplateTypedef(); |
| TemplateTypedef<int> f; // A function that returns a List<int>. |
| TemplateTypedef f; // A function that returns a List<dynamic>. |
| |
| typedef List<T> GenericTypedef<T>(); |
| GenericTypedef f; // A function that is generic. |
| List<int> ints = f<int>(); |
| List<String> strings = f<String>(); |
| ``` |
| |
| Given that this would be a breaking change we explored alternatives that would also solve the other two issues. In particular the new syntax had to work for locals and fields, too. |
| |
| First and foremost the new syntax had to be readable. It also had to solve the three mentioned issues. Finally, we wanted to make sure, we didn't choose a syntax that would hinder future evolution of the language. We made sure that the syntax would work with: |
| - nullability: the syntax must be nullable without too much hassle: |
| ``` dart |
| (int)->int?; // A function that is nullable, or that returns a nullable integer? |
| Problem disappears with <- |
| int <- (int)? ; vs int? <- (int) |
| ``` |
| - union types (in case we ever want them). |
| |
| |
| #### Common Characteristics |
| For all the following proposals we had decided that the arguments could either be just the type, or optionally have a name. For example, `(int)->int` is equivalent to `(int id)->int`. Especially with multiple arguments of the same type, providing a name can make it much easier to reason about the type: `(int id, int priority) -> void`. However, type-wise these parameter names are ignored. |
| |
| All of the proposals thus interpret single-argument identifiers as types. This is in contrast to the old syntax where a single identifier would state the name of the parameter: in `void foo(bar(int)) {...}` the `int` is the name of the parameter to `bar`. This discrepancy is hopefully temporary, as we intend to eventually change the behavior of the old syntax. |
| |
| ##### Right -> Arrow |
| Using `->` as function-type syntax feels very natural and is used in many other languages: Swift, F#, SML, OCaml, Haskell, Miranda, Coq, Kotlin, and Scala (with =>). |
| |
| Examples: |
| ``` dart |
| typedef F = (int) -> void; // Function from (int) to void. |
| typedef F<T> = () -> List<T>; // Template Typedef. |
| typedef F = <T>(T) -> List<T>; // Generic function from T to List<T>. |
| ``` |
| |
| We could even allow a short form when there is only one argument: `int->int`. |
| |
| We have experimented with this syntax: [https://codereview.chromium.org/2439573003/] |
| |
| Advantages: |
| - easy to learn and familiar to many developers. |
| - could support shorthand form `int->int`. |
| |
| Open questions: |
| - support shorthand form? |
| - whitespace. Should it be `(int, int) -> String` or `(int, int)->String`, etc. |
| |
| Disadvantages: |
| - Relatively late token. The parser would have to do relatively big lookaheads. |
| - Works badly with nullable types: |
| ``` dart |
| typedef F = (int) -> int?; // Nullable function or nullable int? |
| // Could be disambiguated as follows: |
| typedef F = ((int)->int)?; // Clearly nullable function. |
| ``` |
| |
| |
| ##### Left <- Arrow |
| This section explores using `<-` as function-type syntax. There is at least one other language that uses this syntax: [Twelf](http://www.cs.cmu.edu/~twelf/guide-1-2/twelf_3.html). |
| |
| Examples: |
| ``` dart |
| typedef F = void <- (int); // Function from (int) to void. |
| typedef F<T> = List<T> <- (); // Template Typedef. |
| typedef F = List<T> <- <T>(T); // Generic function from T to List<T>. |
| ``` |
| |
| Could also allow a short form: `int<-int`. (For some reason this seems to read less nicely than `int->int`.) |
| |
| We have experimented with this syntax: [https://codereview.chromium.org/2466393002/] |
| |
| Advantages: |
| - return value is on the left, similar to normal function signatures. This also simplifies `typedef`s, where the return value is more likely to stay on the first line. |
| - faster to parse, since the `<-` doesn't require a lot of look-ahead. |
| - relatively similar to `->`. |
| - no problems with nullable types: |
| ``` dart |
| typedef F = int <- (int)?; // Nullable function. |
| typedef F = int? <- (int); // Returns nullable int. |
| ``` |
| |
| Open Questions: |
| - whitespace? |
| - support shorthand form? |
| |
| Disadvantages: |
| - `<-` is ambiguous: `x<-y ? foo(x) : foo(y) // if x < (-y) ...`. |
| - Not as familiar as `->`. |
| |
| ##### Function |
| Dart already uses `Function` as general type for functions. It is relatively straightforward to extend the use of `Function` to include return and parameter types. (And no: it's not `Function<int, int>` since that wouldn't work for named arguments). |
| |
| |
| ``` dart |
| typedef F = void Function(int); // Function from (int) to void. |
| typedef F<T> = List<T> Function(); // Template Typedef. |
| typedef F = List<T> Function<T>(T); // Generic function from T to List<T>. |
| ``` |
| |
| This form does not allow any shorthand syntax, but fits nicely into the existing parameter syntax. |
| |
| Before we accepted this syntax, we had experimented with this syntax: [https://codereview.chromium.org/2482923002/] |
| |
| Advantages: |
| - very similar to the syntax of the corresponding function declarations. |
| - no ambiguity. |
| - (almost) no new syntax. That is, the type can be immediately extrapolated from other syntax. |
| - no open questions wrt whitespace. |
| - symmetries with existing use of `Function`: |
| ``` dart |
| Function f; // a function. |
| Function(int x) f; // a function that takes an int. |
| double Function(int) f; // a function that takes an int and returns a double. |
| ``` |
| |
| Disadvantages: |
| - longer. |
| |
| ##### Conclusion |
| We found that the `Function`-based syntax fits nicely into Dart and fulfills all of our requirements. Due to its similarity to function declarations it is also very future-proof. Any feature that works with function declarations should work with the `Function`-type syntax, as well. |
| |