-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Abbreviated function literals #265
Comments
@tatumizer wrote:
Right, it is a potential source of confusion that There is no clash in the technical sense, because it is well-defined when |
Negotiating with @lrhn is an ongoing effort. ;-) |
After a lot of discussions about the use of the name The most obvious choice for the name of the implicit parameter is then On the other hand, @lrhn suggested that we could use the context type to offer two variants: When the context type is a function that takes no arguments a |
Here's a new idea: Any undeclared identifier of the regexp-form Then For nested functions, you need to add extra We can allow you to omit the digit if there is only one parameter, so the above can be This makes simple things wasy: We can then still consider allowing trailing function literals: When a function invocation's last argument is a function literal, Then you can do: list.forEach() {
print($1);
} |
I like the For the trailing function literals, we still have the issue that |
@eernstg what about |
Right, and it may also conflict with another kind of concise function literal: |
I don't think I'll ever accept a syntax for functions which does not have a delimiter. The It's true that simply doing As for |
Just to keep perspective here, this can get out of hand very quickly. Closures themselves are syntax sugar for regular functions, and now we're into sugar-for-sugar territory. I think as closures get more complicated (and nested), we should encourage devs to break them out into simple, separate functions. Here's an example where the readability is about the same, but closures make it very clear what's happening: void printNested(List<List> list) {
list.forEach(=>=>print($$));
// vs
list.forEach(=>$.forEach(print));
// vs
list.forEach( (List sublist) => sublist.forEach(print) );
}
void main() {
List<List> list = [[1, 2, 3], [4, 5, 6]];
printNested(list); // each number on its own line
} And here's an example of a separate function being more readable than all the alternatives: String groupNested1(List<List> list) => list
.map(=>$.map(=>$$.toString().join("")))
.join(", ");
// vs
String groupNested2(List<List> list) => list.map(
(List sublist) => sublist.map(
(value) => value.toString()
).join("")
).join(", ");
// vs
String groupNested3(List<List> list) => [
for (final List sublist in list) [
for (final int value in sublist)
value.toString()
].join("")
].join(", ");
// vs
String toString(Object obj) => obj.toString();
String groupList(List list) => list.map(toString).join("");
String groupNested4(List<List> list) => list.map(groupList).join(", ");
// --------
void main() {
var list = [[1, 2, 3], [4, 5, 6]];
print(groupNested(list)); // 123, 456
} |
Is removing the calling parentheses when an abbreviated function literal being considered? myList.forEach { it.foo(); }; |
@gosoccerboy5 It's a feature of other languages that we are aware of. Whether it's a good fit for Dart depends on whether we can fit it into the existing syntax. If we can, that would be great. |
I think I'll give a tiny bit more context. This is about syntax like The problem with that syntax in Dart is that it does not use a consistent syntactic pattern where declarations start with a reserved word. (For instance, Scala does that, so they use So if we see This means that it isn't something that just works. |
Oh. |
Could `expr` (an expression inside backticks) be a syntax sugar for xs.forEach(`print(it)`); |
That could probably work (backticks are currently not used at all in Dart, so they are very easy for a parser to recognize). They wouldn't support nesting, but abbreviated function literals inside abbreviated function literals aren't going to be very useful anyway. Backticks could be somewhat more concise than braces, because they wouldn't "naturally" imply spaces. Also, the xs.forEach(`print(it)`);
xs.forEach({ print(it); });
xs.forEach(=> print(it));
xs.map(`2 * it`);
xs.map({ return 2 * it; });
xs.map(=> 2 * it); |
I think nested abbreviated function literals can be useful, like (I'm leaning more towards the parameters of the function being derived from the context type, types, positions and names, and treating the parameter list like an implicit record type, so |
This is a proposal to add support for abbreviated function literals to Dart.
It is based on discussions about anonymous methods, and may be used in combination with a pipe operator to achieve expressions of a similar form as an anonymous method. A similar mechanism exists in Kotlin.
[Edit April 24, 2019: Spelled out the design choice of naming the implicit parameter
this
. May 13th: Removed parts about the namethis
and introduced use of context type; see this comment for more details.]Overview
Function literals are used frequently in Dart, and many of them declare exactly zero or one required parameter, and no optional parameters. The declaration of such a parameter can be omitted if a standard name is chosen for it, and if a suitable parameter type can be obtained from type inference.
Syntactically, a block function literal with no parameters already has the form
() { ... }
, so we cannot just omit the parameter declaration in order to obtain an abbreviated declaration. A plain<block>
could be used (that is{ ... }
containing statements). This creates some parsing ambiguities, but they are not hard to resolve (as specified below).It is already a compile-time error to have an expression statement starting with
{
, so there is no ambiguity for a block in a sequence of statements: That is just a regular block, that is, some more statements in a nested static scope.For function literals of the form
(someName) => e
, we can use the abbreviation=> e
, provided thatsomeName
is the name which is used for a parameter which is declared implicitly. In this case there are no syntactic conflicts. Similarly, we can abbreviate() => e
to=> e
. With both abbreviations we need to disambiguate the form with zero parameters and the one with one parameter, but we can do this in a way which is already used in many situations in Dart: Based on the context type.Following Kotlin and using
it
as the name of implicitly declared parameters, here are some examples:Asynchronous and generator variants can be expressed by adding the relevant keyword in front, e.g.,
[2, 3].map(sync* { yield it; yield it; })
, which will evaluate to anIterable<Iterable<int>>
that would print as((2, 2), (3, 3))
.Syntax
The grammar is adjusted as follows in order to support abbreviated function literals:
We insist that a block which is used to specify a function literal cannot be empty. This resolves the ambiguity with set and map literals.
Static Analysis and Dynamic Semantics
Let
e
be an expression of the form<nonEmptyBlock>
that occurs such that it has a context type of the formT Function(S)
for some typesT
andS
;e
is then treated as(it) e
. Similarly, a termB
of the form<functionExpressionBody>
or<functionExpressionWithoutCascadeBody>
with such a context type is treated as(it) B
.Let
e
be an expression of the form<nonEmptyBlock>
that occurs such that does not have a context type of the formT Function(S)
for any typeT
andS
;e
is then treated as() e
. Similarly, a termB
of the form<functionExpressionBody>
or<functionExpressionWithoutCascadeBody>
with such a context type is treated as() B
.This determines the static analysis and type inference, as well as the dynamic semantics.
Discussion
This is a tiny piece of syntactic sugar, but it might be justified by (1) the expected widespread usage, and (2) the standardization effect (which allows developers to read a function at a glance because the chosen parameter name is immediately recognized).
An argument against having this abbreviation is that it creates yet another meaning for an already very frequently seen construct, the
{ ... }
block. The usage of=> e
might be less confusing in this respect, because the token=>
already serves to indicate that "this is a function literal".The proposal makes
{...}
mean() {...}
in the case when there is no context type or only a loose one likedynamic
, and it only means(it) {...}
when the context type is a function type with one positional parameter. We could easily have chosen the opposite, but the given choice is motivated by the typing properties:If we make the opposite choice (such that
{...}
"by default" means(it) {...}
), and the context type isFunction
or any top type (e.g.,dynamic
) then the parameterit
will get the typedynamic
, and this is likely to introduce a large amount of dynamic typing in the body, silently.When the context type doesn't match any of the cases mentioned above we will have a compile-time error. In that case the error message should explicitly say something like "the value expected here cannot be an abbreviated function literal". We may be able to say that the desugaring is undefined in this case, but it seems more practical to decide that
{...}
is desugared to a specific term with a specific type, such that we can emit the normal "isn't assignable to" error message as well.We could support some other argument list shapes. For example,
{...}
could mean({int a, double b}) {...}
when the context type isT Function({int a, double b})
, and we could in general handle named parameters, plus zero or one positional parameter (namedit
). However, it probably wouldn't be very easy to understand such a function body, because the declaration of the named parameters are not shown anywhere locally. Hence, no such mechanisms are included in this proposal.Another thing to keep in mind is that it might be somewhat tricky to see exactly where any given occurrence of the identifier which is the name of the implicitly declared formal parameter is declared:
We believe that this will not be a serious problem in practice, because the widespread use of the implicit parameter name
it
will make it obvious that it is a really bad idea to declare the same name globally.It may be considered confusing to have modifiers like
async
as specified. This is a completely optional part of the proposal, and we could just take it out. Developers would then have to write an explicit parameter part in the case where they want to use an asynchronous function or a generator function.Revisiting the request in #259 and the examples (version 1, 2, and 3) in there, we could express a solution similar to version 4, #260, using abbreviated function literals and the pipe operator (#43) as follows:
The text was updated successfully, but these errors were encountered: