hack类型别名:Opaque

2018-11-21 10:18 更新

一个不透明类型别名使用创建的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经过抽象,代码就不能转换回来,所以抽象不能被破坏。一旦所有的代码都被转换,别名上的约束可以被删除,并且可以是完全不透明的。

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号