-
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
Function literal selectors #344
Comments
|
@natanfudge, thanks for pointing out #342, I hadn't seen that! I added an 'Edit:' paragraph to this issue, pointing out that #342 and this issue have very similar topics. [Edit May 10th: deleted two paragraphs about an ambiguity in the grammar: It has been resolved.] |
Followup on ambiguity: We haven't had any such elements in the Dart syntax before, but it actually comes out quite naturally to disambiguate terms like If there is any whitespace immediately before the syntax which could be either an The proposal and the grammar update CL have been updated accordingly. |
This definitely resolves the ambiguity. However, I think it is mandatory to offer some criticism. It might be quite confusing to have a syntax error resolved with a space - as you said, there aren't such elements in Dart. Additionally a singular space is hard to spot which makes reading difficult in some situations, for example this one:
So my input is this:
|
Thinking about the exact same thing! This kind of syntax is a source of ambiguity for the developer as well as the parser, and it's obvious that we need to very careful about comprehensibility. |
Perhaps a placeholder f0 { xyz; }
f1 (_ foo) { print(foo); } Other than that, I don't see a different way to disambiguate it (as of right now, without using completely different syntax). As an aside, an void Function() lookupFoo(DynamicLibrary lib) {
return lib.lookup<Void Function()>("foo").asFunction(); // [What we use today]
return lib.lookupFunction<Void Function(), _>("foo"); // [lookupFunction is available today, but we can't omit the second parameter type]
} |
Interesting idea! We'd then need a complete set of grammar rules deriving a But we still have to be careful. @lrhn mentioned the following fact: main() {
foo(b) {
print(b);
};
..
} If we omit the semicolon after Case 1: We intended to declare a local function, but added the semicolon by accident: Then maybe there is no Case 2: We intended to perform the invocation, but forgot the semicolon: Then we just get a local function On top of these issues, note that Checking: The keyword |
Having explored this idea a little bit, I've reached the conclusion that function literal selectors will not fit very well into Dart. Syntax like Based on that conclusion I'll close this issue. |
That is extremely disappointing. I was hoping Dart was heading towards a future where
becomes SharedPreferences.getInstance().then (preferences){
setState {
_x = preferences.getBool("x")
}
} Which is so much more pleasant to read. |
@natanfudge wrote:
Right, I want a concise syntax as well. But any syntax that makes the expression However, that's not the only option available. For instance, the proposal for abbreviated function literals (#265) allows you to express the example more concisely. It relies on the context types to determine that the outer function literal takes one argument (whose name is then by definition SharedPreferences.getInstance().then(=> setState(=> _x = it.getBool("x"))); If the function literals contain a larger amount of computations then it might work well to use anonymous methods (#260). An anonymous method is applied to a receiver (which is the term before the period(s)), so we'd want to compute that instance first, and then set the state (I'm assuming that await SharedPreferences.getInstance()..{
setState(=> _x = getBool("x"));
// Could have more statements here, this is just a function body.
}; The invocation of |
[Edit May 13th 2019: Closing this issue; cf. this comment for details.]
In response to #343, this issue is a proposal for function literal selectors, which is a mechanism for passing a function literal as the last actual argument, including abbreviated function literals (#265). This is a tiny syntactic optimization. It can be worthwhile because it could be used frequently (for instance, in code similar to Kotlin type-safe builders).
[Edit May 9th 2019: Note that #342 has a very similar topic. That issue describes a larger number of situations, and this issue contains a concrete syntax proposal which has been tested using Dart.g. May 10th: Resolved disambiguation issue.]
Consider the following tiny Kotlin example of a type-safe builder from here:
There's nothing special about expressing this in Dart:
However, when we use the builder in Kotlin we get to use the special syntax where a lambda (Dart lingo: function literal) is passed as the last argument to a function invocation without parentheses:
// Kotlin expression using the `html` builder. html { body() }
In Dart, we have more syntactic noise:
Using a function literal selector we would get this:
In general, function literal selectors allow for the last parameter to be passed outside the parentheses if it is a function literal, and the parentheses can be omitted entirely in the case where no other parameters are passed (as in the example above). Here's an example with two parameters:
This difference matters when we consider more complex expressions. For instance, this example would have many occurrences of
({ ... });
in Dart (assuming #265, and we'll have a lot more noise without that). This proposal will allow us to drop those parentheses.Syntax
This proposal is intended to allow abbreviated function literals as well as regular function literals as selectors. Hence, the grammar changes in #265 are assumed here.
In addition to that, the grammar is modified as follows in order to allow for function literal selectors:
The full grammar is available as Dart.g in this CL.
The Dart grammar has always relied on rule ordering for disambiguation. For function literal selectors this becomes visible, because we need to disambiguate expressions like
f<X>(){ g(); }
: If we do not handle that issue then it could be parsed as<primary> <argumentPart> <nonEmptyBlock>
and it could also be parsed as<primary> <functionPrimary>
.The only change is that
<selector>
now includes<functionPrimary>
. It occurs as an alternative before<argumentPart>
, but in that case it requires some whitespace, and it occurs after<argumentPart>
with no extra requirements.This means that a selector after whitespace will be parsed as a
<functionPrimary>
if possible; otherwise it will be parsed as an<argumentPart>
. A selector that doesn't come right after some whitespace will be parsed as an<argumentPart>
if possible, otherwise as a<functionPrimary>
. In other words, you include that space, as inf <X>() { g(); }
rather thanf<X>() { g(); }
, when you want to indicate that<X>()
should be considered as a parameter part for the following function body. And you omit the space (as is common) when it should be considered as an argument part.Here is an example using the new syntax:
Static Analysis and Dynamic Semantics
This feature is a piece of syntactic sugar, which means that the static analysis and dynamic semantics is determined by the desugaring step.
The desugaring step transforms a sequence of terms derived from
<selector>
, iterating over it from left to right, as follows. Consider a sequence of selectors s1 s2 ... sk, and assume that desugaring has reached index j for some j in 1 .. k.If sj is derived from
<argumentPart>
and sj+1 is derived from<nonEmptyBlock>
then replace sj sj+1 by s1j, which is obtained from sj by adding sj+1 as the last actual argument, adding a comma if needed.Otherwise, if sj is derived from
<nonEmptyBlock>
then replace sj by (sj).The second case applies when j = k as well as when sj+1 is not derived from
<nonEmptyBlock>
.It is a compile-time error if the desugaring step yields an argument part where a positional argument is passed after a named argument.
The text was updated successfully, but these errors were encountered: