Skip to content

Instantly share code, notes, and snippets.

@eernstg
Last active August 27, 2017 13:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eernstg/42c0dc0c02c0c2f13884ee42f2b7da84 to your computer and use it in GitHub Desktop.
Save eernstg/42c0dc0c02c0c2f13884ee42f2b7da84 to your computer and use it in GitHub Desktop.
Informal specification: Anonymous extension methods

Anonymous Extension Methods

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.

Syntax

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

Static analysis

Dynamic semantics

Changes

  • Originally specified on Aug 20th, 2017.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment