Skip to content

Instantly share code, notes, and snippets.

@eernstg
Last active November 24, 2017 15:52
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/9c9c822953830f6814114303bf1f52a3 to your computer and use it in GitHub Desktop.
Save eernstg/9c9c822953830f6814114303bf1f52a3 to your computer and use it in GitHub Desktop.
Informal specification of generic constructors

Feature: Generic Constructors

Author: eernst@

Status: Under discussion.

Version: 0.1 (2017-11-24)

This document is an informal specification of the support in Dart 2 for generic constructors, that is, constructors that can receive their "own" type arguments, in addition to any type arguments which may be passed to the class of the new instance which is being created.

The main motivation for having generic constructors is that the type arguments passed to the constructor (not the ones for the class of the new instance) can be used to specify typing relationships among constructor arguments that are independent of the ones for the class of the new instance. Here is an example which illustrates that such relationships may exist, but cannot be expressed:

// `Map` is declared somewhat differently, but that does not matter here.
class Map<K, V> {
  Map.fromIterable(
      Iterable iterable, {K key(element), V value(element)}) {...}
  ...
}

The intention is that the Iterable should return an object (whose type is unspecified, i.e., dynamic) at each step, and that object is passed to both key and value (which will accept an actual argument of any type) in order to compute and insert the next key-value pair into the map.

For consistency, it would be much better if we could express the obvious typing relationship: The iterable is an Iterable<T> for some type T, and the two functions accept an actual argument of type T. However, even though the type T is important during construction, it is irrelevant for the new Map which will be created. It would be highly inconvenient to add an extra formal type parameter to the Map class — that would show up as a useless type argument in a huge number of occurrences of Map<K, V, T> as a type, and also in invocations of other constructors. So that is definitely not a solution.

However, if we add support for generic constructors then we could do as follows:

// A modified version of `Map` using a generic constructor.
class Map<K, V> {
  Map.fromIterable<E>(
      Iterable<E> iterable, {K key(E element), V value(E element)}) {...}
  ...
}

main() {
  var map = new Map<int, String>.fromIterable<bool>(
    [true, false],
    (b) => b ? 0 : 1,
    (b) => b ? "Hello, " : "world!",
  );

  var map2 = new Map.fromIterable(
    [true, false],
    (b) => b ? 0 : 1,
    (b) => b ? "Hello, " : "world!",
  );
}

In the modified version, we can use the formal type parameter E of the constructor Map.fromIterable to express the typing relationship mentioned above, and this will help detecting invocations of this constructor where the types do not fit together.

The function main uses this constructor, and it is specified or inferred that E is bool, K is int, and V is String, and the argument types all fit in with that.

Syntax

The grammar must be modified such that constructors can be declared to have formal type parameters, and expressions that invoke constructors can take actual type arguments:

constructorSignature ::=  // Modified
    typeIdentifier formalParameterList |
    typeIdentifier '.' identifier formalParameterPart
constructorDesignation ::=
    typeName '.' identifier typeArguments |
    typeName typeArguments ('.' identifier typeArguments?)?
    qualified

An update to the specification grammar shows how this can be integrated into a complete grammar.

Static Analysis

Dynamics Semantics

Revisions

  • 0.1 (Nov 24th 2017): .
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment