Generics: Introduction

Certain types (classes, interfaces, and traits) and their methods can be parameterized; that is, their declarations can have one or more placeholder names called type parameters that are associated with types via type arguments when a class is instantiated or a method is called. A type or method having such placeholder names is called a generic type or generic method, respectively. Top-level functions can also be parameterized giving rise to generic functions.

An example of a generic class is Vector<T>, from the Hack collections implementation. T is called a type parameter, and it is what makes Vector generic. It can hold any type of value, from int to an instance of a class. However, for any instantiation of the class, once a type has been associated with T, it cannot be changed to hold any other type.

<?hh

namespace Hack\UserDocumentation\Generics\Intro\Examples\Vec;

/* Signature of Vector
*
* class Vector<Tv> implements MutableCollection<Tv> {
* :
* }
*
*/

function main_vec(): void {
  $x = Vector {1, 2, 3, 4}; // T is associated with int
  var_dump($x);
  $y = Vector {'a', 'b', 'c', 'd'}; // T is associated with string
  var_dump($y);
}

main_vec();
Output
object(HH\Vector)#1 (4) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
  [3]=>
  int(4)
}
object(HH\Vector)#2 (4) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "b"
  [2]=>
  string(1) "c"
  [3]=>
  string(1) "d"
}

$x is a Vector<int>, while $y is a Vector<string>. A Vector<int> and Vector<string> are not the same type.

Methods and functions can also be generic. One use case is when they need to manipulate generic classes:

<?hh

namespace Hack\UserDocumentation\Generics\Intro\Examples\GenericMethods;

// Testing generic methods in a non-generic class.

class Box<T> {
  public T $value;
  public function __construct(T $v) {
    $this->value = $v;
  }
}

function swap<T>(Box<T> $a, Box<T> $b) : void {
  $temp = $a->value;
  $a->value = $b->value;
  $b->value = $temp;
}

function do_int_swap(): void {
  $y = new Box(3);
  $z = new Box(4);
  echo $y->value." ".$z->value;
  swap($y, $z);
  echo $y->value." ".$z->value;
}

function do_string_swap(): void {
  $y = new Box('a');
  $z = new Box('b');
  echo $y->value." ".$z->value;
  swap($y, $z);
  echo $y->value." ".$z->value;
}

function doAll(): void {
  do_int_swap();
  do_string_swap();
}

doAll();
Output
3 44 3a bb a

The above example shows a generic function swap<T>() operating on a generic Box<T> class.

Generics allow developers to write one class or method with the ability to be parameterized by any type, all while preserving type safety. Without generics, accomplishing a similar model would require creating BoxInt and BoxString classes, and that quickly gets verbose. Alternatively, we could treat $value as a mixed type and do instanceof() checks, which means that inserting a string into a box of int would not raise a typechecker error, but would only be discovered at runtime.

Arity

The arity of a generic type or method is the number of type parameters declared for that type or method. As such, class Vector has arity 1. The Hack library generic container class Map implements an ordered, dictionary-style collection. This type has arity 2, and utilizes a key type and a value type, so the type Map<int, Employee>, for example, could be used to represent a group of Employee objects indexed by an integer employee number.

Within a generic parameter list, the parameter names must

  • be distinct
  • all begin with the letter T
  • not be the same as that used for a generic parameter for an enclosing class, interface, or trait.

In the following case, class Vector has one type parameter, Tv, so has arity 1. Method map also has one type parameter, Tu, so has arity 1.

final class Vector<Tv> implements MutableVector<Tv> {

  public function map<Tu>((function(Tv): Tu) $callback): Vector<Tu> { … }
}

In the following case, class Map has two type parameters, Tk and Tv, so has arity 2. Method zip has one, Tu, so has arity 1.

final class Map<Tk, Tv> implements MutableMap<Tk, Tv> {

  public function zip<Tu>(Traversable<Tu> $iter): Map<Tk, Pair<Tv, Tu>> { … }
}

In the following case, function maxValue has one type parameter, T, so has arity 1.

function maxValue<T>(T $p1, T $p2): T { … }