dart-lang / dart-lang/language

Support concise function literals

Open
#8 31 comments 49 reactions 0 assignees View on GitHub
request
Dominant language
TeX
Stars
2.9k
Forks
239
Avg merge
2d 18h
Merged PRs (30d)
14

Description

## Motivation for concise function literals

The syntax for a function literal includes parentheses and `=>` or parentheses and braces, such that we may specify both the formal parameters and a function body. However, given that inference will frequently obtain type annotations for the parameters from the context, the formal parameter specification often specifies the name only. This means that we may obtain a more concise syntax for function literals if we introduce some level of support for default parameter names.

This might be very convenient, e.g., for reducing `xs.map((x) => x.toString())` to `xs.map(#.toString())` or even `xs.map(.toString())`.

Note this [thread on dart-language-discuss](https://groups.google.com/a/google.com/d/msgid/dart-language-discuss/CAFnZOmYaE9%2BzGsGaouU%3DKawiVtJASkEZbY7eowzNdMmPqH--cw%40mail.gmail.com?utm_medium=email&utm_source=footer) which is one of the many locations where this discussion has occurred.

Note also that an old language team issue presented several of these ideas ([here](https://github.com/dart-lang/dart-lang-evolution/issues/76)), but the repository does not currently admit public access.

## Just omit the parenthesis

With this, `(x) => e` could be abbreviated to `x => e`, and possibly `(x) { S }` to `x { S }`. It would only eliminate two characters, but it would generally work everywhere, and hence it might be useful to do independently of the other proposals described below.

## Using `#` as a default parameter name

We could let `#` denote an implicitly declared formal parameter, such that express `(x) => x.foo(x.bar)` could be abbreviated as `#.foo(#.bar)`. In general, an expression `e` containing some number of occurrences of `#` would stand for `(x) => [x/#]e` where `x` is a fresh variable name, and `[x/#]` is the textual substitution operation that replaces all occurrences of `#` by `x` in its argument, here: `e`. (There is no need to worry about variable capture because `x` is fresh.)

The main issue with this approach is that it is ambiguous: `#.foo(#.bar)` might mean `(x) => x.foo((y) => y.bar)` as well as `(x) => x.foo(x.bar)`.

We could resolve the ambiguity in several ways:

- Require that the parameter is used exactly once in the body of the function; that is, we can abbreviate `(x) => x.foo(42)` as `#.foo(42)`, but `(x) => x.foo(x.bar)` cannot be abbreviated to `#.foo(#.bar)`, that would instead mean `(x) => x.foo((y) => y.bar)`.

- Include the braces in the abbreviation, that is, we can abbreviate `(x) { f(x); x.g(42); }` as `{ f(#); #.g(42); }`. This could create ambiguities with a block of statements if used as an expressionStatement, where `{ f(#); #.g(42); }` would mean `{ (x) => f(x); (y) => y.g(42); }`, but both of these are rather useless (we just create some function objects and discard them), so we may simply be able to make all such nonsense an error.

- Include the arrow in the abbreviation, that is, we can abbreviate `(x) => x.foo(42)` as `=> #.foo(42)` and `(x) => x.foo(x.bar)` to `=> #.foo(#.bar)`.

It might be possible to take this approach with various other characters in addition to `#`, e.g., `@`, `%`, `?` were suggested in the above-mentioned dart-language-discuss discussion. Each of them would of course have different implications for the possible choices of grammar, that is, for the syntactic forms of abbreviated function literals that we can allow.

## Use a designated identifier as the default parameter name

We might use a regular identifier like `_` or `it` rather than `#` as the default parameter name, in which case there is no need to change the grammar. The perceived readability of the resulting code might be better or worse. It probably doesn't make much difference when it comes to the implementation effort.

But it might make the change more breaking, because there may be existing code which is then reinterpreted to have a new meaning, e.g., we already have `xs.map(_.foo)` somewhere, and `_` is in scope such that the code works and means the same thing as `xs.map((x) => _.foo(x))`. In that situation, it would be highly error-prone to give it the new meaning `xs.map((x) => x.foo)`, and it would presumably be a nightmare to try to use rules like "`_.foo` is desugared to `(x) => x.foo` if and only if `_` is undefined in the current scope".

Otherwise, the ambiguities mentioned for `#` would apply in this case as well, and the fixes could essentially be reused.

## Use the empty string as the default parameter name

This would allow us to abbreviate `(x) => x.foo(42)` to `.foo(42)`, which might be unambiguous in the grammar, but there are only few expressions where this would work. For instance, we cannot abbreviate `(x) => o.foo(x)` to `o.foo()` because that already means something else, which might just as well be the intended meaning.

So this approach might look very attractive with certain examples, but it is unlikely to scale up.

## Multiple default parameter names

The above-mentioned [dart-language-discuss thread](https://groups.google.com/a/google.com/d/msgid/dart-language-discuss/CAFnZOmYaE9%2BzGsGaouU%3DKawiVtJASkEZbY7eowzNdMmPqH--cw%40mail.gmail.com?utm_medium=email&utm_source=footer) also had several ideas about how to enable functions receiving multiple (positional and required) parameters to be abbreviated.

For instance, `$1`, `$2`, ... could be used, or `$`, `$$`, ..., or `_1`, `_2`, ..., such that `(x, y) => x + y` could be abbreviated as `$1 + $2`.

In return for restricting each parameter to occur exactly once and in order, we could also use the same symbol for all parameters, such that `(x, y) => x + y` could be abbreviated as, for instance, `_ + _` or `$ + $`.

The former may look somewhat busy, and the latter is certainly rather restrictive, but these ideas can essentially be piled on top of all the previous proposals in order to let them support the multi-argument case.

## Use a designated form of identifier as the default parameter names

We could say that `$foo` is a default parameter name just because it starts with `$`, and so is `$bar`. If we thus reserve all identifiers of a specific form as default parameter names, then we can express the situation where different occurrences are the same or not the same parameter, and we can also communicate more clearly what each parameter is intended to mean: `(x, y) => x + y` could be abbreviated as `$x + $y`, `(x) => x + x` could be abbreviated as `$ + $`, and:

```dart
... myWidgets.map((widget) => widget.getColor).firstWhere((color) => color == Blue) ...

// could be abbreviated into:
... myWidgets.map($widget.getColor).firstWhere($color == Blue) ...

// which may be easier to read than this:
... myWidgets.map(_.getColor).firstWhere(_ == Blue) ...
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.