hack类型别名:Opaque
一个不透明类型别名使用创建的newtype。与透明类型别名不同,小心组织源代码,编译器可以确保通用代码不能直接访问不透明别名的基础类型。
没有类型约束的别名
每个不透明的别名类型都不同于其基础类型,也不同于其他任何类型的别名。只有包含opaque类型别名定义的文件中的源代码被允许访问底层的实现。
考虑一个文件,point.inc.php它包含一个2D点类型和一些函数原语的不透明的别名定义:
<?hh
namespace Hack\UserDocumentation\TypeAliases\Opaque\Examples\AliasNoConstraint;
// point.php - Point implementation file
newtype Point = (int, int);
function createPoint(int $x, int $y): Point {
return tuple($x, $y);
}
function setX(Point $p, int $x): Point {
$p[0] = $x;
return $p;
}
function setY(Point $p, int $y): Point {
$p[1] = $y;
return $p;
}
function getX(Point $p): int {
return $p[0];
}
function getY(Point $p): int {
return $p[1];
}
只有那些需要知道Point
底层结构的函数应该在上面的Point
实现文件中定义。所有支持该Point
类型的通用函数都可以驻留在PointFunctions.php中,如下所示:
<?hh
namespace Hack\UserDocumentation\TypeAliases\Opaque\Examples\AliasNoConstraint;
// point-functions.php - Point's supporting functions
function distance_between_2_Points(Point $p1, Point $p2): float {
$dx = getX($p1) - getX($p2);
$dy = getY($p1) - getY($p2);
return sqrt($dx*$dx + $dy*$dy);
}
这里是一些创建和使用点的代码:
<?hh
namespace Hack\UserDocumentation\TypeAliases\Opaque\Examples\AliasNoConstraint;
// test-point.php - User code that tests type Point
function run(): void {
$p1 = createPoint(5, 3);
var_dump($p1);
$p2 = createPoint(9, -5);
var_dump($p2);
$dist = distance_between_2_Points($p1, $p2);
echo "distance between points is " . $dist ."\n";
// But we cannot pass a tuple of two ints since they are not a Point
// This will give a Hack typechecker error
$will_not_type_check = distance_between_2_Points(tuple(2, 3), tuple(3, 4));
// However, the code will still run in HHVM
echo "distance between points is " . $will_not_type_check ."\n";
}
run();
/*
Here is the type error for $will_not_type_check
test-point.php:18:52,62: Invalid argument (Typing[4110])
point-functions.inc.php:9:36,40:
This is an object of type
Hack\UserDocumentation\TypeAliases\Opaque\Examples\AliasNoConstraint\Point
test-point.php:18:52,62: It is incompatible with a tuple
test-point.php:18:65,75: Invalid argument (Typing[4110])
point-functions.inc.php:9:47,51:
This is an object of type
Hack\UserDocumentation\TypeAliases\Opaque\Examples\AliasNoConstraint\Point
test-point.php:18:65,75: It is incompatible with a tuple
*/
Output
array(2) {
[0]=>
int(5)
[1]=>
int(3)
}
array(2) {
[0]=>
int(9)
[1]=>
int(-5)
}
distance between points is 8.9442719099992
distance between points is 1.4142135623731
与别名定义相同的文件,功能createPoint和朋友有---和需要---直接访问任何点的元组中的整数字段。但是,任何其他文件不。
类型约束的别名
考虑一个包含以下不透明类型定义的文件:
<?hh
newtype Counter = int ;
任何包含这个文件的文件都不知道a Counter
是一个整数,所以包含文件不能在该类型上执行任何类似整数的操作。这是一个主要的限制,因为抽象类型的所谓的精心选择的名称Counter
,表明其价值可能增加和/或减少。我们可以通过向别名的定义添加一个类型约束来“解决”这个问题,如下所示:
<?hh
newtype Counter as int = int;
类型约束的存在允许将不透明类型视为具有由类型约束指定的类型,这将删除一些别名的不透明。尽管约束的存在允许将别名类型隐式转换为约束类型,但是没有相反的方向定义转换。在这个例子中,这意味着a Counter
可以被隐式地转换成一个int
,而不是相反的。下面的例子会因为这个原因而无法检查:
<?hh
// Assume this code is in a different file than where the Counter type is
// defined.
class A {
public Counter $c;
public function __construct() {
// This is prohibited, as there is no implicit conversion from int
// (the type of 0) to Counter
$this->c = 0;
}
}
类型约束必须是被别名的类型的子类型。
在下面的例子中,Point有一个约束(int, int); 因此我们可以通过Point任何方法期待(int, int)...但反之亦然!
<?hh
namespace Hack\UserDocumentation\TypeAliases\Opaque\Examples\AliasConstraint;
// point-constraint.inc.php - Point implementation file
newtype Point as (int, int) = (int, int);
function createPoint(int $x, int $y): Point {
return tuple($x, $y);
}
function setX(Point $p, int $x): Point {
$p[0] = $x;
return $p;
}
function setY(Point $p, int $y): Point {
$p[1] = $y;
return $p;
}
function getX(Point $p): int {
return $p[0];
}
function getY(Point $p): int {
return $p[1];
}
上面的两个例子激发了几个用例,在这样的不透明类型别名中戳洞。
在这个Counter例子中,我们可能对a的值Counter以及它的维护方式有额外的限制,因此需要不透明度来确保合适的不变量得到尊重。这意味着我们不能让任何人int成为一个Counter。但是换个方式就好了; 做一个Counter有道理的数学。
对于Point例如,它可能看起来像我们在很大程度上打破了抽象Point,而事实上我们。你可能不想编写看起来像这样的新代码。但是,在转换现有的非类型代码时,它可能非常有用。我们可以引入一个新的Point不透明别名,但是有一个类型限制,用于向后兼容。任何新的代码都会使用这个Point类型,从而受到Point抽象及其不变性的影响。(int, int)如果需要,现有的代码可以继续直接在元组上工作。但是,如果不Point经过抽象,代码就不能转换回来,所以抽象不能被破坏。一旦所有的代码都被转换,别名上的约束可以被删除,并且可以是完全不透明的。