Author: eernst@
Status: Under discussion.
This document is an informal specification of the anonymous extension method feature in Dart. This feature allows for writing a function in an expression whose semantics makes it similar to an instance method on the result of an evaluation.
The motivation for having this feature is that it allows many programming tasks to be expressed concisely and with good readability and comprehensibility. Moreover, it allows for a statically safe expression of certain usages of generic types which could otherwise not be expressed.
For instance,
e.{ foo(); x = 10; }
evaluates e
to an instance o, then calls foo()
on o, and
finally sets the field x
on o to 10.
It also allows for named access to the result of the evaluation. For instance
var y = e.(it){ foo(); print(it); return x; };
evaluates e
to an instance o, binds it
to o and then invokes
it.foo()
, and print(it)
using that binding of it
, and finally
evaluates it.x
, which is the value that is assigned to y
. The
parameter it
which denotes the receiver which will implicitly be
the target of member accesses in the function body gets the static
type which is the static type of e
, and it is final
.
Finally, an anonymous extension method supports named access to the actual type arguments of the result of the evaluation. For instance,
(<int>[1, 2] as List<num>).<E>(it){
var y = e;
if (y is E) it.add(y);
}
The type parameter list must declare the same number of type
parameters as declared for the static type of the result of the
evaluation (here: 1, because that static type is List<num>
), and
the dynamic semantics ensures that the value of E
is exactly the
actual type argument of it
at the type List
(here: int
).
Hence, this construct allows for writing statically safe code
containing operations which may otherwise be unsafe due to covariant
generics. It should be noted that such guarantees cannot otherwise
be obtained in Dart.
This matters in particular when a generic class type argument is used as a generic method bound. In that case it is required that an invocation of the generic method receives a type argument (either explicitly or inferred, but the type argument value must in both cases be expressed), but there is no expression which denotes a type argument which is known to satisfy the bound. This mechanism provides such an expression:
class A<T> {
T t;
A(this.t);
T foo<S extends T>(S s) {
T tmp = t;
t = s;
return tmp;
}
}
main() {
A<num> a = new A<int>(42);
num n1 = a.foo<num>(3.14); // Mal-bounded!: Requires type argument <int>.
num n2 = a.<T>(it){
// But this is safe.
return foo<T>(3.14 is T? 3.14 : 3);
}
}
Comparing anonymous extension methods to cascades, there are several
similarities: Both mechanisms allow for performing member accesses several
times without having several occurrences of an expression that denotes the
receiver; e.g., both mechanisms can be used to call several methods on the
same receiver, or set the value of several fields. On the other hand,
dispatching parameters apply to a complete function body whereas cascades
are expressions; hence, cascades only allow a few types of code whereas
dispatching parameters allow for all kinds of statement, e.g., control
structures like if
and await for
, and function literals. Finally,
cascades offer no way to handle covariance safely.
The grammar is adjusted as follows, based on the specification grammar in this CL, patch set 15. The rule marked CHANGED is changed, other rules are new:
cascadeSection ::= // CHANGED
'..' cascadeHeadSelector cascadeTailSelector*
(assignmentOperator expressionWithoutCascade)?
cascadeHeadSelector ::=
cascadeSelector arguments* | anonymousMethod
cascadeTailSelector ::=
assignableSelector arguments* | anonymousMethodSelector
selector ::=
assignableSelector | anonymousMethodSelector | arguments
anonymousMethodSelector ::=
'.' anonymousMethod
anonymousMethod ::=
typeParameters? ('(' identifier ')')? block
- Originally specified on Aug 20th, 2017.