hack類型別名:Opaque

2018-11-21 10:18 更新

一個不透明類型別名使用創(chuàng)建的newtype。與透明類型別名不同,小心組織源代碼,編譯器可以確保通用代碼不能直接訪問不透明別名的基礎(chǔ)類型。

沒有類型約束的別名

每個不透明的別名類型都不同于其基礎(chǔ)類型,也不同于其他任何類型的別名。只有包含opaque類型別名定義的文件中的源代碼被允許訪問底層的實現(xiàn)。

考慮一個文件,point.inc.php它包含一個2D點類型和一些函數(shù)原語的不透明的別名定義:

<?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實現(xiàn)文件中定義。所有支持該Point類型的通用函數(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)建和使用點的代碼:

<?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和朋友有---和需要---直接訪問任何點的元組中的整數(shù)字段。但是,任何其他文件不。

類型約束的別名

考慮一個包含以下不透明類型定義的文件:

<?hh 
newtype  Counter  =  int ;

任何包含這個文件的文件都不知道a Counter是一個整數(shù),所以包含文件不能在該類型上執(zhí)行任何類似整數(shù)的操作。這是一個主要的限制,因為抽象類型的所謂的精心選擇的名稱Counter,表明其價值可能增加和/或減少。我們可以通過向別名的定義添加一個類型約束來“解決”這個問題,如下所示:

<?hh
newtype Counter as int = int;

類型約束的存在允許將不透明類型視為具有由類型約束指定的類型,這將刪除一些別名的不透明。盡管約束的存在允許將別名類型隱式轉(zhuǎn)換為約束類型,但是沒有相反的方向定義轉(zhuǎn)換。在這個例子中,這意味著a Counter可以被隱式地轉(zhuǎn)換成一個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];
}

上面的兩個例子激發(fā)了幾個用例,在這樣的不透明類型別名中戳洞。

在這個Counter例子中,我們可能對a的值Counter以及它的維護方式有額外的限制,因此需要不透明度來確保合適的不變量得到尊重。這意味著我們不能讓任何人int成為一個Counter。但是換個方式就好了; 做一個Counter有道理的數(shù)學(xué)。

對于Point例如,它可能看起來像我們在很大程度上打破了抽象Point,而事實上我們。你可能不想編寫看起來像這樣的新代碼。但是,在轉(zhuǎn)換現(xiàn)有的非類型代碼時,它可能非常有用。我們可以引入一個新的Point不透明別名,但是有一個類型限制,用于向后兼容。任何新的代碼都會使用這個Point類型,從而受到Point抽象及其不變性的影響。(int, int)如果需要,現(xiàn)有的代碼可以繼續(xù)直接在元組上工作。但是,如果不Point經(jīng)過抽象,代碼就不能轉(zhuǎn)換回來,所以抽象不能被破壞。一旦所有的代碼都被轉(zhuǎn)換,別名上的約束可以被刪除,并且可以是完全不透明的。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號