hack泛型:子类型
与亚型相关的一个看似与直觉相反的领域是泛型。
<?hh
namespace Hack\UserDocumentation\Generics\Subtypes\Examples\Intuitive;
function echo_add(num $x, num $y): void {
echo $x + $y;
}
function get_int(): int {
return rand();
}
function run(): void {
$num1 = get_int();
$num2 = get_int();
echo_add($num1, $num2); // int is a subtype of num
}
run();
Output
2229204202
由于int是一个子类型num,类型转换器通过传递int一个函数是完全正确的num。如果你传递一个float函数就没问题了。如果你传递一个int和一个float函数,也没关系。
但是,你认为类型分析者应该接受吗?
<?hh
namespace Hack\UserDocumentation\Generics\Subtypes\Examples\CounterIntuitive;
class Box<T> {
private Vector $box;
public function __construct(int $firstItem) {
$this->box = Vector {$firstItem};
}
public function add(T $v) {
$this->box[] = $v;
}
}
function addRandomToBox(Box<num> $x): void {
$x->add(rand());
}
function createBox(): Box<int> {
return new Box(3);
}
function run(): void {
$box = createBox(); // we have a Box<int>
addRandomToBox($box); // typechecker cannot guarantee a Box<int> now.
var_dump($box); // HHVM doesn't care since we erase generics anyway.
}
run();
Output
object(Hack\UserDocumentation\Generics\Subtypes\Examples\CounterIntuitive\Box)#2 (1) {
["box":"Hack\UserDocumentation\Generics\Subtypes\Examples\CounterIntuitive\Box":private]=>
object(HH\Vector)#1 (2) {
[0]=>
int(3)
[1]=>
int(123434323)
}
}
这似乎是应该是有效的传递Box<int>一个函数期望一个Box<num>自从int是一个子类型num。然而,在泛型的情况下,子类型关系与其原始类型的对应关系并不一致。
原因是因为你的泛型对象是通过引用传递的,所以类型检查者没有办法安全地知道你是否正在修改Boxin addRandomToBox()以包含不是的东西num。虽然明显地向我们说,我们正在添加int中addRandomToBox(),该typechecker实际上不考虑发生了什么。所以不确定的是,返回给我们的还是一个Box<int>。
HHVM运行时并不在意,因为我们在运行时会删除泛型。
不变的集合和数组
由于不可变的集合不能被改变array(即拷贝而不是引用),泛型与上面讨论的子类型关系实际上会通过类型检查器。这是因为类型检查器可以保证实体在返回给调用者时不会被改变。
<?hh
namespace Hack\UserDocumentation\Generics\Subtypes\Examples\Immutable;
function addRandomToArray(array<num> $x): void {
$x[] = 3.2; // this is a copy, not a reference
}
function createArray(): array<int> {
return array(3);
}
function run(): void {
$arr = createArray(); // we have a array<int>
// typechecker CAN guarantee array<int> now since what is received by
// addRandomToArray() is a copy (passed-by-value)
addRandomToArray($arr);
var_dump($arr); // Still only going to contain 3, not the 3.2.
}
run();
Output
array(1) {
[0]=>
int(3)
}