dart-sdk/pkg/dev_compiler/doc/GENERIC_METHODS.md
2016-03-04 05:51:45 -08:00

7.2 KiB

Prototype Syntax for Generic Methods

Generic methods are a proposed addition to the Dart language.

This is a summary of the current (as of January 2016) comment-based generic method syntax supported by the analyzer strong mode and the Dart Dev Compiler. The comment-based syntax essentially uses the proposed actual generic method syntax, but wraps it in comments. This allows developers to experiment with generic methods while still ensuring that their code runs on all platforms while generic methods are still being evaluated for inclusion into the language.

Declaring generic method parameters

Generic method parameters are listed using a block comment after the method or function name, inside of angle brackets.

// This declares a function which takes two unused generic method parameters
int f/*<S, T>*/(int x) => 3;

As with classes, you can put bounds on type parameters.

// This declares a function which takes two unused generic method parameters
// The first parameter (S) must extend num
// The second parameter (T) must extend List<S>
int f/*<S extends num, T extends List<S>>*/(int x) => 3;

Class methods (instance and static) can be declared to take generic parameters in the same way.

class C {
  static int f/*<S, T>*/(int x) => 3;
  int m/*<S, T>*/(int x) => 3;
}

Function typed parameters, local functions, and function expressions can also be declared to take generic parameters.

// foo takes a generic method as a parameter
void foo(int f/*<S>*/(int x)) {}

// foo declares a local generic function
void foo() {
  int f/*<S>*/(int x) => 3;
  return;
}

// foo binds a generic function expression to a local variable.
void foo() {
  var x = /*<S>*/(int x) => x;
}

We do not currently support a way to declare a function as returning a generic function. This will eventually be supported using something analogous to Dart typedefs.

Using generic method parameters

The previous examples declared generic method parameters, but did not use them. You can use a generic method parameter T anywhere that a type is expected in Dart by writing a type followed by /*=T*/. So for example, dynamic /*=T*/ will be interpreted as dynamic by all non-strong mode tools, but will be interpreted as T by strong mode. In places where it is valid to leave off a type, simply writing /*=T*/ will be interpreted as dynamic by non-strong mode tools, but will be interpreted as T by strong mode. For example:

// foo is a generic method which takes a single generic method parameter S.
// In strong mode, the parameter x will have type S, and the return type will
// be S
// In normal mode, the parameter x will have type dynamic, and the return
// type will be dynamic.
dynamic/*=S*/ foo/*<S>*/(dynamic/*=S*/ x) { return x; }

This can be written more concisely by leaving off the dynamic.

/*=S*/ foo/*<S>*/(/*=S*/ x) {return x;}

You can also put a type to the left of the /*=T/. This type will be used for all non-strong mode tools. For example:

// This method works with `int`, `double`, or `num`. The return type will
// match the type of the parameters.
num/*=T*/ pickAtRandom/*<T extends num>*/(num/*=T*/ x, num/*=T*/ y) { ... }

Note that the generic parameter is in scope in the return type of the function, in the argument list of the function, and in the body of the function. When declaring local variables and parameters, you can also use the /*=T*/ syntax with var.

// foo is a generic method that takes a single generic parameter S, and a value
// x of type S
void foo/*<S>*/(var /*=S*/ x) {
  // In strong mode, y will also have type S
  var /*=S*/ y = x;

  // In strong mode, z will also have type S
  dynamic /*=S*/ z = y;
}

Anywhere that a type literal is expected, you can also use the /*=T*/ syntax to produce a type literal from the generic method parameter.

void foo/*<S>*/(/*=S*/ x) {
  // In strong mode, s will get the type literal for S
  Type s = dynamic /*=S*/;

  // In strong mode, this attempts to cast 3 as type S
  var y = (3 as dynamic /*=S*/);
}

You can use the /*=T*/ syntax to replace any type with a generic type parameter, but you will usually want to replace dynamic. Otherwise, since the original type is used at runtime, it may cause checked mode errors:

List/*<T>*/ makeList/*<T extends num>*/() {
  return new List<num /*=T*/>();
}

void main() {
  List<int> list = makeList/*<int>*/(); // <-- Fails here.
}

This program checks without error in strong mode but fails at runtime in checked mode since the list that gets created is a List<num>. A better choice is:

List/*<T>*/ makeList/*<T extends num>*/() {
  return new List/*<T>*/();
}

void main() {
  List<int> list = makeList/*<int>*/();
}

Instantiating generic classes with generic method parameters

You can use generic method parameters to instantiate generic classes using the same /*=T*/ syntax.

// foo is a generic method which returns a List<S> in strong mode,
// but which returns List<dynamic> in normal mode.
List<dynamic /*=S*/> foo/*<S>*/(/*=S*/ x) {
   // l0 is a list literal whose reified type will be List<S> in strong mode,
   // and List<dynamic> in normal mode.
   var l0 = <dynamic /*=S*/>[x];

   // as above, but with a regular constructor.
   var l1 = new List<dynamic /*=S*/>();
   return l1;
}

In most cases, the entire type argument list to the generic class can be enclosed in parentheses, eliminating the need for explicitly writing dynamic.

// This is another way of writing the same code as above
List/*<S>*/ foo/*<S>*/(/*=S*/ x) {
   // The shorthand syntax is not yet supported for list and map literals
   var l0 = <dynamic /*=S*/>[x];

   // but with regular constructors you can use it
   var l1 = new List/*<S>*/();
   return l1;
}

Instantiating generic methods

Generic methods can be called without passing type arguments. Strong mode will attempt to infer the type arguments automatically. If it is unable to do so, then the type arguments will be filled in with whatever their declared bounds are (by default, dynamic).

class C {
  /*=S*/ inferableFromArgument/*<S>*/(/*=S*/ x) { return null;}
  /*=S*/ notInferable/*<S>*/(int x) { return null;}
}

void main() {
  C c = new C();
  // This line will produce a type error, because strong mode will infer
  // `int` as the generic argument to fill in for S
  String x = c.inferableFromArgument(3);

  // This line will not produce a type error, because strong mode is unable
  // to infer a type and will fill in the type argument with `dynamic`.
  String y = c.notInferable(3);
}

In the case that strong mode cannot infer the generic type arguments, the same syntax that was shown above for instantiating generic classes can be used to instantiate generic methods explicitly.

void main() {
  C c = new C();
  // This line will produce a type error, because strong mode will infer
  // `int` as the generic argument to fill in for S
  String x = c.inferableFromArgument(3);

  // This line will produce a type error in strong mode, because `int` is
  // explicitly passed in as the argument to use for S
  String y = c.notInferable/*<int>*/(3);
}