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.
Generics allow programmers to write a class or method with the ability to be parameterized to any set of types, all while preserving type safety.
Consider the following example in which VecStack
is a generic class having one type parameter, T
:
use namespace HH\Lib\{C, Vec};
interface StackLike<T> {
public function isEmpty(): bool;
public function push(T $element): void;
public function pop(): T;
}
class StackUnderflowException extends \Exception {}
class VecStack<T> implements StackLike<T> {
private int $stackPtr;
public function __construct(private vec<T> $elements = vec[]) {
$this->stackPtr = C\count($elements) - 1;
}
public function isEmpty(): bool {
return $this->stackPtr === -1;
}
public function push(T $element): void {
$this->stackPtr++;
if (C\count($this->elements) === $this->stackPtr) {
$this->elements[] = $element;
} else {
$this->elements[$this->stackPtr] = $element;
}
}
public function pop(): T {
if ($this->isEmpty()) {
throw new StackUnderflowException();
}
$element = $this->elements[$this->stackPtr];
$this->elements[$this->stackPtr] = $this->elements[0];
$this->stackPtr--;
return $element;
}
}
As shown, the type parameter T
is used in the declaration of the instance property $elements
, as a parameter for push()
, and as a return type for pop()
.
function useIntStack(StackLike<int> $stInt): void {
$stInt->push(10);
$stInt->push(20);
echo 'pop => '.$stInt->pop()."\n"; // 20
$stInt->push(30);
echo 'pop => '.$stInt->pop()."\n"; // 30
echo 'pop => '.$stInt->pop()."\n"; // 10
// $stInt->push(10.5); // rejected as not being type-safe
}
The line commented-out, attempts to call push with a non-int
argument. This is rejected, because $stInt
is a stack of int
.
The arity of a generic type or method is the number of type parameters declared for that type or method. As such, class Stack
has arity 1.
Here is an example of a generic function, swap
, having one type parameter, T
:
function swap<T>(inout T $i1, inout T $i2): void {
$temp = $i1;
$i1 = $i2;
$i2 = $temp;
}
<<__EntryPoint>>
function main(): void {
$v1 = -10;
$v2 = 123;
echo "\$v1 = ".$v1.", \$v2 = ".$v2."\n";
swap(inout $v1, inout $v2);
echo "\$v1 = ".$v1.", \$v2 = ".$v2."\n";
$v3 = "red";
$v4 = "purple";
echo "\$v3 = ".$v3.", \$v4 = ".$v4."\n";
swap(inout $v3, inout $v4);
echo "\$v3 = ".$v3.", \$v4 = ".$v4."\n";
}
$v1 = -10, $v2 = 123
$v1 = 123, $v2 = -10
$v3 = red, $v4 = purple
$v3 = purple, $v4 = red
The function swaps the two arguments passed to it. In the case of the call with two int
arguments, int
is inferred as
the type corresponding to the type parameter T
. In the case of the call with two string
arguments,
string
is inferred as the type corresponding to the type parameter T
.