一個(gè)不透明類(lèi)型別名使用創(chuàng)建的newtype。與透明類(lèi)型別名不同,小心組織源代碼,編譯器可以確保通用代碼不能直接訪(fǎng)問(wèn)不透明別名的基礎(chǔ)類(lèi)型。
每個(gè)不透明的別名類(lèi)型都不同于其基礎(chǔ)類(lèi)型,也不同于其他任何類(lèi)型的別名。只有包含opaque類(lèi)型別名定義的文件中的源代碼被允許訪(fǎng)問(wèn)底層的實(shí)現(xiàn)。
考慮一個(gè)文件,point.inc.php它包含一個(gè)2D點(diǎn)類(lèi)型和一些函數(shù)原語(yǔ)的不透明的別名定義:
<?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
底層結(jié)構(gòu)的函數(shù)應(yīng)該在上面的Point
實(shí)現(xiàn)文件中定義。所有支持該Point
類(lèi)型的通用函數(shù)都可以駐留在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);
}
這里是一些創(chuàng)建和使用點(diǎn)的代碼:
<?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和朋友有---和需要---直接訪(fǎng)問(wèn)任何點(diǎn)的元組中的整數(shù)字段。但是,任何其他文件不。
考慮一個(gè)包含以下不透明類(lèi)型定義的文件:
<?hh
newtype Counter = int ;
任何包含這個(gè)文件的文件都不知道a Counter
是一個(gè)整數(shù),所以包含文件不能在該類(lèi)型上執(zhí)行任何類(lèi)似整數(shù)的操作。這是一個(gè)主要的限制,因?yàn)槌橄箢?lèi)型的所謂的精心選擇的名稱(chēng)Counter
,表明其價(jià)值可能增加和/或減少。我們可以通過(guò)向別名的定義添加一個(gè)類(lèi)型約束來(lái)“解決”這個(gè)問(wèn)題,如下所示:
<?hh
newtype Counter as int = int;
類(lèi)型約束的存在允許將不透明類(lèi)型視為具有由類(lèi)型約束指定的類(lèi)型,這將刪除一些別名的不透明。盡管約束的存在允許將別名類(lèi)型隱式轉(zhuǎn)換為約束類(lèi)型,但是沒(méi)有相反的方向定義轉(zhuǎn)換。在這個(gè)例子中,這意味著a Counter
可以被隱式地轉(zhuǎn)換成一個(gè)int
,而不是相反的。下面的例子會(huì)因?yàn)檫@個(gè)原因而無(wú)法檢查:
<?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;
}
}
類(lèi)型約束必須是被別名的類(lèi)型的子類(lèi)型。
在下面的例子中,Point有一個(gè)約束(int, int); 因此我們可以通過(guò)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];
}
上面的兩個(gè)例子激發(fā)了幾個(gè)用例,在這樣的不透明類(lèi)型別名中戳洞。
在這個(gè)Counter例子中,我們可能對(duì)a的值Counter以及它的維護(hù)方式有額外的限制,因此需要不透明度來(lái)確保合適的不變量得到尊重。這意味著我們不能讓任何人int成為一個(gè)Counter。但是換個(gè)方式就好了; 做一個(gè)Counter有道理的數(shù)學(xué)。
對(duì)于Point例如,它可能看起來(lái)像我們?cè)诤艽蟪潭壬洗蚱屏顺橄驪oint,而事實(shí)上我們。你可能不想編寫(xiě)看起來(lái)像這樣的新代碼。但是,在轉(zhuǎn)換現(xiàn)有的非類(lèi)型代碼時(shí),它可能非常有用。我們可以引入一個(gè)新的Point不透明別名,但是有一個(gè)類(lèi)型限制,用于向后兼容。任何新的代碼都會(huì)使用這個(gè)Point類(lèi)型,從而受到Point抽象及其不變性的影響。(int, int)如果需要,現(xiàn)有的代碼可以繼續(xù)直接在元組上工作。但是,如果不Point經(jīng)過(guò)抽象,代碼就不能轉(zhuǎn)換回來(lái),所以抽象不能被破壞。一旦所有的代碼都被轉(zhuǎn)換,別名上的約束可以被刪除,并且可以是完全不透明的。
更多建議: