-
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
Support concise function literals #8
Comments
I vote for _ ... myWidgets.map(_.getColor).firstWhere(_ == Blue) ...
... myWidgets.map(_.size > 5 && _.width > 2)
numbers.filter(_ > 0 && _ < 10) It is short and clear) |
I like Another option for solving that (apart from the three mentioned above) might be to delimit to the nearest enclosing production of a specific grammar rule. I don't think it will work though. Something like x.forEach(y.add((_) => _))
x.forEach((_) => y.add(_))
(_) => x.forEach(y.add(_)) This can only really be resolved using types, and we can't infer types before knowing the expression structure. I'd propose |
" contains no syntactic way to distinguish the three options " |
I'd much prefer using In Kotlin, function literals use the syntax // single parameter omitted.
foo({ it * 2 })
// single parameter specified.
foo({ x -> x * 2 })
// multiple parameters specified.
foo({ x, y -> x * y }) I find this syntax very concise and easy to read. |
@mpfaff said:
I don't know Kotlin extensively, but I know that in Kotlin we don't have a proper syntax for collection literals. I.e., to make a set we do I think that it would not be trivial to disambiguate the example you gave. Maybe the static analysis would be able to infer properly the type based on what the function |
Note that this proposal puts a lot of emphasis on avoiding any syntactic top-level structure that unambiguously implies that the term is a function literal. So we're basically just writing an expression containing some special subexpressions (like This means that we can have a very concise notation: The other side of the coin is that it is ambiguous how much of an expression we need to include: The proposal about 'abbreviated function literals', #265, is focused on abbreviated forms where this ambiguity does not exist. That proposal covers such cases as So, @mpfaff and @mateusfccp, it's possible that #265 matches your preferences more directly. |
Here's my old proposal from that closed repo: We could add a new syntax to create a closure with an implicit parameter list. Scala has a neat placeholder syntax for this, but I think it's a little too magical. So how about we say: You can define a closure using Here's some comparing the syntax when the receiver is the (possibly implicit) // today placeholder params
() => getter => getter
(value) => setter = value => setter = _
() => -this => -this
(arg) => this + arg => this + _
(arg) => this[arg] => this[_]
(arg, value) => this[arg] = value => this[_] = _
() => this[expr] => this[expr]
(value) => this[expr] = value => this[expr] = _ And when it's a given object: // today placeholder params
() => obj.getter => obj.getter
(value) => obj.setter = value => obj.setter = _
() => -obj => -obj
(arg) => obj + arg => obj + _
(arg) => obj[arg] => obj[_]
(arg, value) => obj[arg] = value => obj[_] = _
() => obj[expr] => obj[expr]
(value) => obj[expr] = value => obj[expr] = _ Here are some other examples where it would be nice to be able to make a little closure and where the existing tear-off syntax doesn't help because we aren't partially applying the receiver: connectorRegions[connector].map((region) => merged[region]);
connectorRegions[connector].map(=> merged[_]);
return region.reduce((a, b) => a + b) ~/ region.length;
return region.reduce(=> _ + _) ~/ region.length;
conditions.forEach((condition) => condition.update(action));
conditions.forEach(=> _.update(action));
var openDirs = dirs.where((dir) => _isOpen(hero, dir));
var openDirs = dirs.where(=> _isOpen(hero, _));
return slots.where((item) => item != null).iterator;
return slots.where(=> _ != null).iterator;
game.hero.heroClass.commands.firstWhere((command) => command.canUse(game));
game.hero.heroClass.commands.firstWhere(=> _.canUse(game));
_effects = _effects.where((effect) => effect.update(game)).toList();
_effects = _effects.where(=> _.update(game)).toList();
rules = chunks
.map((chunk) => chunk.rule)
.where((rule) => rule != null)
.toSet()
.toList(growable: false),
rules = chunks
.map(=> _.rule)
.where(=> _ != null)
.toSet()
.toList(growable: false), Looking at this today, I'm iffy about using |
FWIW I have no problem using |
Why require
|
See #8 (comment) for why undelimited "functions" are a problem. |
@munificent, how would you convert this using (arg) => this [arg] = recompute(arg) If each => this [_] = recompute(_)
// translates to
(index, value) => this [index] = recompute(value)
// which is closest to your example of:
(arg, value) => this[arg] = value => this[_] = _ How about using |
My pitch was that in cases like this, you have to use an explicit So if you want to support an implicitly named parameter like
My hunch (which I'd want to scrape a corpus to get real data on first) is that 2 is a lot more common than 1. I think it's pretty rare to use the same parameter multiple times, but higher-order functions like |
I agree that 2 is probably much more common than 1, but I think it's way less intuitive to say "the same identifier can refer to different variables depending on how many times it appears in the code previously". Not to mention the bugs that can arise -- refactoring out one Personally, I think Dart's closure syntax is currently very clear, readable, and logical, and that should be prioritized. Sugar is nice, but let's keep perspective and make sure the logic stays absolutely clear. Worst case scenario: a closure has to broken out into its own function -- not the end of the world. |
I'll just chime in with another option:
Examples:
Can also use Or |
I would prefer not using
|
Ironically, we are making wildcards first-class in the language, which is probably a requirement for this issue to be implemented with the Personally, I feel more like |
Which I am in favour of! Have I missed something? |
If I understand correctly, as we currently can assign a value to (_) {
final list = someList.map(_.doSomething()); // Does this _ refer to the value received as argument in the outer function or to the argument passed to map?
} Although probably most of the cases could be disambiguated by the context type, it could be still confusing. However, if we have first class wildcard support, in the code above I may be wrong, tho, because I didn't read all the details and discussions about wildcards. |
that seems to solve the problem of the analyzer/compiler figuring out what is meant, after some thought, I can see how "we omit this" can feel intuitive, when applied to this new syntax usage of I have no data on how widespread either of these conceptions are, I do like the idea of using |
The "invent a name" has only really been defined to work for a single parameter. If we use the names of the type parameters in the context type, or use the "record field name" of the corresponding argument list as a record ( That does mean using |
What is the shortcut for 1-parameter function? |
With my suggestion: |
Unless extension on (int, int) {
foo() {
print($1);
var x = =>$1 + 1; // what $1 is it?
}
} ( Edit: after staring at the expression The reason the kotlin's syntax (kind of) works in kotlin is that there, the anonymous function has a form of |
What is the function type of that expression even? There is no context type, so there is no hint what the implicit parameter list should look like. That leaves two options:
Whether it's the first or last, it wouldn't introduce new names. An implicit parameter list will only introduce new names if it occurs in a context type that tells it which parameters are needed. And then the user should be expecting precisely those parameters. |
Not everything is lost. The idea can be revived by introducing the syntax Let's suppose dart introduces the syntax like The syntax should support more than a single expression in the body. If we want to invoke the lambda like IIFE, we write |
That feels like an uncomfortable amount of new syntax rules and meanings just for this specific feature, in my opinion. |
For 2 features, another being #3065 |
Q: will it be a breaking change if dart adds support for non-semicolon-terminated expression foo() { 42; } // current return type: Null
foo() { 42; 0 } // return type int (implicit return) |
Just a note that some of the more common forms of this issue can be solved with #3786: xs.map((x) => x.toString())
xs.map(X.toString);
myWidgets.map((widget) => widget.getColor).firstWhere((color) => color == Blue)
myWidgets.map(Widget.getColor).firstWhere((color) => color == Blue) In general, any closure of the form It would only work in cases where you're calling a method with no parameters, or a method/operator with more than one value, but I believe that should already cover quite a lot of cases without introducing ambiguities. |
I've been writing some C# lately, and when coming back to Dart, I really don't like having to add parenthesis to a single callback param. Writing |
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())
toxs.map(#.toString())
or evenxs.map(.toString())
.Note this thread on dart-language-discuss 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), but the repository does not currently admit public access.
Just omit the parenthesis
With this,
(x) => e
could be abbreviated tox => e
, and possibly(x) { S }
tox { 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 nameWe 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 expressione
containing some number of occurrences of#
would stand for(x) => [x/#]e
wherex
is a fresh variable name, and[x/#]
is the textual substitution operation that replaces all occurrences of#
byx
in its argument, here:e
. (There is no need to worry about variable capture becausex
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
_
orit
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 asxs.map((x) => _.foo(x))
. In that situation, it would be highly error-prone to give it the new meaningxs.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)
too.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 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:The text was updated successfully, but these errors were encountered: