3.2 Type Parameters

Haxe allows parametrization of a number of types, as well as class fields and enum constructors. Type parameters are defined by enclosing comma-separated type parameter names in angle brackets <>. A simple example from the Haxe Standard Library is Array:

class Array<T> {
  function push(x : T) : Int;
}

Whenever an instance of Array is created, its type parameter T becomes a monomorph. That is, it can be bound to any type, but only one at a time. This binding can happen either:

  • explicitly, by invoking the constructor with explicit types (new Array<String>()) or
  • implicitly, by type inference for instance, when invoking arrayInstance.push("foo").

Inside the definition of a class with type parameters, the type parameters are an unspecific type. Unless constraints are added, the compiler has to assume that the type parameters could be used with any type. As a consequence, it is not possible to access the fields of type parameters or cast to a type parameter type. It is also not possible to create a new instance of a type parameter type unless the type parameter is generic and constrained accordingly.

The following table shows where type parameters are allowed:

Parameter onBound uponNotes
ClassinstantiationCan also be bound upon member field access.
Enuminstantiation
Enum Constructorinstantiation
FunctioninvocationAllowed for methods and named local lvalue functions.
Structureinstantiation

As function type parameters are bound upon invocation, they accept any type if left unconstrained. However, only one type per invocation is accepted. This can be utilized if a function has multiple arguments:

class Main {
  static public function main() {
    equals(1, 1);
    // runtime message: bar should be foo
    equals("foo", "bar");
    // compiler error: String should be Int
    equals(1, "foo");
  }

  static function equals<T>(expected:T, actual:T) {
    if (actual != expected) {
      trace('$actual should be $expected');
    }
  }
}

Both of the equals function's arguments, expected and actual, have type T. This implies that for each invocation of equals, the two arguments must be of the same type. The compiler permits the first call (both arguments being of Int) and the second call (both arguments being of String) but the third attempt causes a compiler error due to a type mismatch.

Trivia: Type parameters in expression syntax

We often get the question of why a method with type parameters cannot be called as method<String>(x). The error messages the compiler gives are not very helpful. However, there is a simple reason for that: the above code is parsed as if both < and > were binary operators, yielding (method < String) > (x).