hack泛型:字符实体
泛型可以用于你习惯于用Hack编程的许多实体,包括类,函数,方法,接口,类型别名和特性。
Classes
考虑下面的例子,其中Stack是一个具有一个类型参数的泛型类T:
<?hh
namespace Hack\UserDocumentation\Generics\Entities\Examples\Classes;
class StackUnderflowException extends \Exception {}
class Stack<T> {
private array<T> $stack;
private int $stackPtr;
public function __construct() {
$this->stackPtr = 0;
$this->stack = array();
}
public function push(T $value): void {
$this->stack[$this->stackPtr++] = $value;
}
public function pop(): T {
if ($this->stackPtr > 0) {
return $this->stack[--$this->stackPtr];
} else {
throw new StackUnderflowException();
}
}
}
function useIntStack(Stack<int> $stInt): void {
$stInt->push(10);
$stInt->push(20);
$stInt->push(30);
echo 'pop => ' . $stInt->pop() . "\n";
$stInt->push(10.5); // rejected as not being type-safe
echo 'pop => ' . $stInt->pop() . "\n";
}
function run(): void {
$s = new Stack();
$s->push(5);
useIntStack($s);
}
run();
Output
pop => 30
pop => 10.5
如图所示,type参数T用于实例属性声明中$stack,作为实例方法的参数类型push,以及作为实例方法的返回类型pop。请注意,虽然push并pop使用类型参数,但它们本身不是通用方法。
该行$stInt->push(10.5);尝试push使用非int参数进行调用。这是被拒绝的,因为$stInt是一堆int,我们正试图推一个float。
功能
下面是一个泛型函数的例子maxVal,有一个类型参数T:
<?hh
namespace Hack\UserDocumentation\Generics\Entities\Examples\Functions;
function maxVal<T>(T $p1, T $p2): T {
return $p1 > $p2 ? $p1 : $p2;
}
function run(): void {
var_dump(maxVal(10, 20));
var_dump(maxVal(15.6, -20.78));
var_dump(maxVal('red', 'green'));
}
run();
Output
int(20)
float(15.6)
string(3) "red"
该函数返回传递给它的两个参数中较大的一个。在调用的情况下maxVal(10, 20),如果这两个参数的类型都是int,则推断为与类型参数对应的类型T,并int返回一个值。在呼叫的情况下maxVal(15.6, -20.78),T被推断为float,而在maxVal('red', 'green'),T被推断为string。
方法
虽然push和pop方法在Stack上面的类示例中的泛型类中定义,但它们本身并不是泛型的。它们已经绑定到类类型参数T。
就像泛型函数一样,泛型方法也有自己的类型参数,即使该方法不属于泛型类。考虑库类型Pair:
final class Pair<Tv1, Tv2> implements ConstVector<mixed> {
// …
public function map<Tu>( (function(Tv): Tu) $callback ): Vector<Tu>
public function zip<Tu>(Traversable<Tu> $iter): Vector<Pair<mixed, Tu>>
public function zip<Tu>( Traversable<Tu> $iterable ): Vector<Pair<mixed, Tu>>
}
正如我们所看到的,方法map和zip每个方法都有一个通用参数Tu,它的类型是从传递给每个方法的参数中推断出来的。这个泛型参数意味着我们可以在方法的参数或返回类型中使用它。请注意,泛型方法具有与类不同的类型参数(例如Tvvs Tu)。如果绑定了这些方法Tv,那么我们就不需要方法的泛型参数,因为它已经绑定到了类的类型参数。
接口
像一个类一样,一个接口可以有类型参数; 例如:
<?hh
namespace Hack\UserDocumentation\Generics\Entities\Examples\Interfaces;
interface MyCollection<T> {
public function put(T $item): void;
public function get(): ?T;
}
class MyStack<T> implements MyCollection<T> {
private Vector<T> $storage;
public function __construct() {
$this->storage = Vector {};
}
public function put(T $item): void {
$this->storage[] = $item;
}
public function get(): ?T {
// LIFO
return $this->storage->count() > 0 ? $this->storage[0] : null;
}
}
class MyQueue<T> implements MyCollection<T> {
private Vector<T> $storage;
public function __construct() {
$this->storage = Vector {};
}
public function put(T $item): void {
$this->storage[] = $item;
}
public function get(): ?T {
// FIFO
return $this->storage->count() > 0
? $this->storage[$this->storage->count() - 1]
: null;
}
}
function processCollection<T>(MyCollection<T> $p1): void {
var_dump($p1->get());
}
function run(): void {
$s = new MyStack();
$s->put(5);
$s->put(9);
$s->put(3);
processCollection($s);
$q = new MyQueue();
$q->put(5);
$q->put(9);
$q->put(3);
processCollection($q);
}
run();
Output
int(5)
int(3)
在这里,我们有通用的堆栈和队列类,每个类都实现相同的通用接口,使这些类能够以一致的方式存储和检索元素。
Traits
像通用类一样,通用特征具有类型参数列表; 例如:
trait MyTrait<T1, T2> {
public static function f(T1 $value): void {
// ...
}
类型别名
类型别名可以是任何类型的别名,包括泛型类型。例如:
newtype Matrix<T> = Vector<Vector<T>>;
type Serialized<T> = string; // T is not used