hack类型系统

2018-10-12 11:02 更新

在Hack中描述类型主要通过Hack源代码中的显式注释来完成。Hack有很多可能的注释类型。您可以在我们的表格中查看每种类型的摘要。

Common Primitives

PHP中可用的主要基本类型可在Hack中作为显式类型注释使用。这些包括:

  • bool
  • int
  • float
  • string
  • array
  • resource
<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Primitive;

class A {
  protected float $x;
  public string $y;

  public function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }
  public function foo(bool $b): float {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }
}

function bar(): string {
  // local variables are inferred, not explicitly typed
  $a = new A();
  if ($a->foo(true) > 8.0) {
    return "Good " . $a->y;
  }
  return "Bad " . $a->y;
}

var_dump(bar());

Output

string(8) "Good Day"

Alias Primitives

Hack 不支持Alias primitives。因此,以下不是要在类型注释中使用的有效类型:

  • boolean
  • integer
  • real
  • double

void

void是一种特殊的原始类型,这意味着函数或方法不返回可观察值。您可以return;在void功能中使用。

注意:void只能用于方法或函数返回。它不适用于属性或参数。

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Void;

class A {
  protected float $x;
  public string $y;

  public function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }
  public function foo(bool $b): float {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }
}

// void can only be used as a return types
function bar(): void {
  // local variables are inferred, not explicitly typed
  $a = new A();
  if ($a->foo(true) > 8.0) {
    echo "Good " . $a->y;
  } else {
    echo "Bad " . $a->y;
  }
}

bar();

Output

Good Day

In Async

async函数返回是比较常见的Awaitable<void>

。这意味着虽然功能本身正在等待返回,但是等待的结果将没有价值。这实际上意味着异步函数做了一些不需要调用者返回值的异步操作。

noreturn

noreturn是一种特殊的原始类型,这意味着函数或静态方法从不返回值。类似于void,但是你甚至不能使用return;具有返回类型的函数noreturn。

noreturn用于表示给定的函数或静态方法总是引发异常,或以某种方式终止函数本身中的程序。

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\NoReturn;

class A {
  protected float $x;
  public string $y;

  public function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }
  public function foo(bool $b): float {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }

  // no return cannot be on an instance method
  // only functions and static class methods
  public static function baz(bool $b): noreturn {
    if ($b) {
      throw new \Exception("No Return");
    } else {
      exit(1);
    }
    return; // Even this will cause type-errors
  }
}

// void can only be used as a return types
function bar(): void {
  // local variables are inferred, not explicitly typed
  $a = new A();
  if ($a->foo(true) > 8.0) {
    echo "Good " . $a->y;
  } else {
    echo "Bad " . $a->y;
  }
  A::baz(false);
}

bar();

Output

Good Day

注意:

只有静态方法和功能

noreturn只能用于函数或静态方法返回。

实例方法不能是noreturn。这是由于类型检查器的分析阶段发生的顺序。在控制流分析期间无法确定实例方法调用的返回类型,因为它需要知道左侧的类型->,并且类型推断的结果尚不可用。调用静态方法不是问题,因为在推断类型之前可以解决这些问题。

noreturn 不适用于属性或参数。

对象

您可以使用任何内置或自定义类或接口的名称。

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Obj;

class Z {
  public function create_A(): A {
    return new A();
  }
}

class A {
  protected float $x;
  public string $y;

  public function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }
  public function foo(bool $b): float {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }
}

// We are taking a Z and returning an object of type A
function baz(Z $z): A {
  return $z->create_A();
}

function bar(): string {
  // local variables are inferred, not explicitly typed
  $z = new Z();
  $a = baz($z);
  if ($a->foo(true) > 8.0) {
    return "Good " . $a->y;
  }
  return "Bad " . $a->y;
}

var_dump(bar());

Output

string(8) "Good Day"

mixed

mixed本质上是一个全部的类型,表示任何可能的Hack值(包括null和void)。

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Mixed;

class A {
  public float $x;
  protected string $y;

  public function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }
  // mixed is the most lax type. Use it only when necessary
  public function foo(bool $b): mixed {
    return $b ? 2.3 * $this->x : $this->y;
  }
}

function bar(): string {
  // local variables are inferred, not explicitly typed
  $a = new A();
  $v = $a->foo(false);
  // Since A::foo() returns a mixed, we need to do various checks to make sure
  // that we let the typechecker know understand what is coming back.
  if (is_float($v)) {
    return "No String";
  }
  invariant(is_string($v), "Something went wrong if this isn't true");
  return "Good " . $v;
}

var_dump(bar());

Output

string(8) "Good Day"

稀疏使用

有一些有用的用途mixed,但是一般来说,您希望尽可能具体地使用您的打字,因为类型检查器只能这么做,mixed因为它的约束是如此松散。

this

this只能用作类的方法的返回类型注释。this表示该方法返回定义了该方法的同一个类的对象。

返回的主要目的this是允许在类本身或其子类的实例上链接方法调用。

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\ThisChaining;

class Vehicle {
  private ?int $numWheels;
  private ?string $make;

  public function setNumWheels(int $num): this {
    $this->numWheels = $num;
    return $this;
  }

  public function setMake(string $make): this {
    $this->make = $make;
    return $this;
  }
}

class Car extends Vehicle {
  private ?bool $autoTransmission;

  public function setAutomaticTransmission(bool $automatic): this {
    $this->autoTransmission = $automatic;
    return $this;
  }
}

class Hybrid extends Car {
  private ?bool $pluggable;

  public function setPluggable(bool $pluggable): this {
    $this->pluggable = $pluggable;
    return $this;
  }

  public function drive(): void {}
}


function run(): void {
  $h = new Hybrid();
  // $h->NumWheels(4) returns the instance so you can immediately call
  // setMake('Tesla') in a chain format, and so on. Finally culminating in an
  // actionable method call, drive().
  $h->setNumWheels(4)
    ->setMake('Tesla')
    ->setAutomaticTransmission(true)
    ->setPluggable(true)
    ->drive();
  var_dump($h);
}

run();

Output

object(Hack\UserDocumentation\Types\TypeSystem\Examples\ThisChaining\Hybrid)#1 (4) {
  ["pluggable":"Hack\UserDocumentation\Types\TypeSystem\Examples\ThisChaining\Hybrid":private]=>
  bool(true)
  ["autoTransmission":"Hack\UserDocumentation\Types\TypeSystem\Examples\ThisChaining\Car":private]=>
  bool(true)
  ["numWheels":"Hack\UserDocumentation\Types\TypeSystem\Examples\ThisChaining\Vehicle":private]=>
  int(4)
  ["make":"Hack\UserDocumentation\Types\TypeSystem\Examples\ThisChaining\Vehicle":private]=>
  string(5) "Tesla"
}

this一个static方法意味着一个类方法返回与调用方法相同类的对象。您可以使用它从static返回类似的类方法返回一个对象的实例new static()

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\ThisStatic;

class A {
  protected float $x;
  public string $y;

  // typechecker error if constructor isn't final because new static() cannot
  // be called to return an instance of a subclass
  final protected function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }

  public function foo(bool $b): float {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }

  // The this type annotation allows you to return an instance of a type
  public static function create(int $x): this {
    $instance = new static();
    if ($x < 4) {
      $instance->x = floatval($x);
    }
    return $instance;
  }
}

function bar(): string {
  // local variables are inferred, not explicitly typed
  // There is no public constructor, so call A's create() method
  $a = A::create(2);
  if ($a->foo(true) > 8.0) {
    return "Good " . $a->y;
  }
  return "Bad " . $a->y;
}

var_dump(bar());

Output

string(7) "Bad Day"

num

num是特殊的联合类型int和float。通常,在Hack中,ints和floats是不兼容的类型。但是,实现了许多数值操作函数的工作方式类似,无论你是传递一个整数还是一个浮点数。num用于这些情况。

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Num;

class A {
  protected num $x;
  public string $y;

  public function __construct(num $x) {
    $this->x = $x;
    $this->y = "Day";
  }
  public function foo(bool $b): num {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }
  // The $x property can be either a float or int
  public function setNum(num $x): void {
    $this->x = $x;
  }
}

function check(A $a): string {
  if ($a->foo(true) > 8.0) {
    return "Good " . $a->y;
  }
  return "Bad " . $a->y;
}

function bar(): string {
  // local variables are inferred, not explicitly typed
  // Setting the $x property in A to an int
  $a = new A(4);
  $ret = check($a);
  // Now setting to a float
  $a->setNum(0.4);
  $ret .= "##" . check($a);
  return $ret;
}

var_dump(bar());

Output

string(17)“Good Day ## Bad Day”

arraykey

arraykey是特殊的联合类型int和string。数组和集合类型可以由int或键入string。假设,例如,对数组执行了一个操作来提取密钥,但是你不知道密钥的类型。你被使用mixed或做某种重复的代码。arraykey解决了这个问题。

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\ArrayKey;

class A {
  protected float $x;
  public string $y;

  public function __construct(float $x) {
    $this->x = $x;
    $this->y = "Day";
  }
  public function foo(bool $b): float {
    return $b ? 2.3 * $this->x : 1.1 * $this->x;
  }
}

// This function can return either a string or an int since it is typed to
// return an arraykey
function bar(): arraykey {
  // local variables are inferred, not explicitly typed
  $a = new A(0.9);
  if ($a->foo(true) > 8.0) {
    return "Good " . $a->y;
  }
  return 5;
}

var_dump(bar());

Output

int(5)

XHP

键入XHP对象时使用两个XHP接口:XHPChild和XHPRoot。

XHPRoot 是任何对象,它是XHP类的一个实例。

XHPChild是echoXHP上下文(例如,echo <div>{$xhpobj}</div>;)中的一组有效类型。这包括原始类型string,int以及float这些类型的数组加上任何XHP对象。

<?hh

// Namespaces and XHP have issues right now

// A custom class extends :x:element and has a render method that returns
// XHPRoot so that you can do something like echo "<custom-class />;" This
// automatically calls the render method
class :ts-simple-xhp extends :x:element {
  public function render(): XHPRoot {
    return <b>Simple</b>;
  }
}

class TSPage {
  protected string $link;
  protected string $title;

  public function __construct(string $title, string $link) {
    $this->link = $link;
    $this->title = $title;
  }

  // return XHPChild when rendering a UI element and the elements
  // of that render are valid for XHP (e.g., strings, arrays of ints, etc.)
  public function render_page(): XHPChild {
    return <div>{$this->title}...{$this->link}</div>;
  }

  public function get_simple(): XHPRoot {
    return <ts-simple-xhp />;
  }
}

function ts_xhp_sample(): void {
  $p = new TSPage("Test XHP", "http://internet.org");
  echo $p->render_page();
  echo PHP_EOL;
  echo $p->get_simple();
}

ts_xhp_sample();

Output

<div>Test XHP...http://internet.org</div>
<b>Simple</b>

Nullable

nullable类型由?放置为类型本身的前缀(例如,?int)来表示。这只是意味着该值可以是该类型或null.

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Nullable;

class A {
  protected float $x;
  public string $y;

  public function __construct() {
    $this->x = 4.0;
    $this->y = "Day";
  }

  // We can pass a nullable as a parameter as well as being nullable on the
  // return type. Properties can also be nullable
  public function foo(?bool $b): ?float {
    return ($b || $b === null) ? 2.3 * $this->x : null;
  }
}

// The ? means that the function can return null in addition to the string
function bar(): ?string {
  // local variables are inferred, not explicitly typed
  $a = new A();
  if ($a->foo(null) === null) {
    return null;
  }
  return "Good " . $a->y;
}

var_dump(bar());

Output

string(8) "Good Day"

什么不能为空?

void,noreturn不能为空,因为它null是一个有效且可观察的返回值。

至于mixed已经允许值null,也可以不写?mixed。

泛型

泛型允许特定的代码以类型安全的方式对付多种类型。根据指定的类型参数,通用类型可以对应一种类型或许多类型。Box<T>例如,可以传递给它的类型是最容许的。array<int>是最少的允许,因为int只允许放置在数组中。

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Generics;

// This is a generic class that is parameterized by T. T can be bound to any
// type, but once it is bound to that type, it must stay that type. It
// can be bound to mixed.
class Box<T> {

  private array<T> $contents;

  public function __construct() {
    $this->contents = array();
  }

  public function put(T $x): void {
    $this->contents[] = $x;
  }

  public function get(): array<T> {
    return $this->contents;
  }
}

// This is a generic function. You parameterize it by putting the type
// parameters after the function name
function gift<T>(Box<T> $box, T $item): void {
  $box->put($item);
}

function ts_generics_1(): array<string> {
  $box = new Box();
  gift($box, "Hello");
  gift($box, "Goodbye");
  // can't do this because the typechecker knows by our return statement and
  // our return type that we are binding the Box to a string type. If we did
  // something like ": array<arraykey>", then it would work.
  // This will work when running in HHVM though.
  gift($box, 3);
  return $box->get();
}

function ts_generics_2(): array<arraykey> {
  $box = new Box();
  gift($box, "Hello");
  gift($box, "Goodbye");
  gift($box, 3);
  return $box->get();
}

function run(): void {
  var_dump(ts_generics_1());
  var_dump(ts_generics_2());
}

run();
    

Output

array(3) {
  [0]=>
  string(5) "Hello"
  [1]=>
  string(7) "Goodbye"
  [2]=>
  int(3)
}
array(3) {
  [0]=>
  string(5) "Hello"
  [1]=>
  string(7) "Goodbye"
  [2]=>
  int(3)
}

Enums

enum

 是常量,通常彼此相关的由一类型。与类常量等不同,enum是Hack类型系统中的一流类型。因此,它们可以用作原语或对象类型的任何地方的类型注释。

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Enum;

enum Color: string {
  BLUE = "blue";
  RED = "red";
  GREEN = "green";
}

// Enums can be used as type annotations just like any other type.
function render_color(Color $c): void {
  echo $c;
}

render_color(Color::BLUE); // "blue"
render_color(Color::RED); // "red"

Output

bluered

可调用

有一个callable类型,但是Hack不允许它(HHVM接受它,但是如果你不关心类型检查器错误)。

相反,Hack提供了一种更具表现力的可调用类型:

function(0..n parameter types): return type
<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Call;

function use_callable(
  Vector<int> $vec,
  (function(int) : ?int) $callback,
): Vector<?int> {
  $ret = Vector {};
  foreach ($vec as $item) {
    $ret[] = $callback($item);
  }
  return $ret;
}

function ts_callable(): void {
  $callable = function(int $i): ?int {
    return $i % 2 === 0 ? $i + 1 : null;
  };
  var_dump(use_callable(Vector {1, 2, 3}, $callable));
}

ts_callable();

// Returns
/*
object(HH\Vector)#3 (3) {
  [0]=>
  NULL
  [1]=>
  int(3)
  [2]=>
  NULL
}
*/

Output

object(HH\Vector)#3 (3) {
  [0]=>
  NULL
  [1]=>
  int(3)
  [2]=>
  NULL
}

元组(Tuples

元组提供指定可能不同类型的固定数量值的类型。元组最常见的用法是从函数返回多个值。

(type1,...,type n)

元组就像固定数组。您不能从元组中删除或更改任何类型,但可以更改每种类型的值。要创建一个元组,您使用与数组相同的语法,但s / arraytuple

tuple(value1, ..., value n);
<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Tup;

// You don't use the keyword tuple when annotating with one
// You do use the keyword tuple when forming one.
function q_and_r(int $x, int $y): (int, int, bool) {
  return tuple(round($x / $y), $x % $y, $x % $y === 0);
}

function ts_tuple(): void {
  // Tuples lend themselves very well to list()
  list($q, $r, $has_remainder) = q_and_r(5, 2);
  var_dump($q);
  var_dump($r);
  var_dump($has_remainder);
}

ts_tuple();

Output

float(3)
int(1)
bool(false)

封面下的数组

在HHVM中,元组被实现为数组,您可以调用is_array()它们并获取true返回值。

Type Aliases

Type aliases

 允许您为现有类型添加新名称。它们可以像注释中的现有类型一样使用。

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\TypeAlias;

type ID = int;
type Name = string;

class Customers {
  private array<ID, Name> $c;
  public function __construct() {
    $this->c = array();
    $this->c[0] = "Joel";
    $this->c[1] = "Fred";
    $this->c[2] = "Jez";
    $this->c[3] = "Tim";
    $this->c[4] = "Matthew";
  }

  public function get_name(ID $id): ?Name {
    if (!array_key_exists($id, $this->c)) {
      return null;
    }
    return $this->c[$id];
  }

  public function get_id(Name $name): ?ID {
    $key = array_search($name, $this->c);
    return $key ? $key : null;
  }
}

function ts_type_alias(): void {
  $c = new Customers();
  var_dump($c->get_name(0));
  var_dump($c->get_id("Fred"));
  var_dump($c->get_id("NoName"));
}

ts_type_alias();

Output

string(4) "Joel"
int(1)
NULL

Classname

Foo::class在PHP中是指包含完整限定名称的字符串常量Foo。

Hack引入了一个特殊类别的别名classname<T>。所以,现在当有人写的时候Foo::class,Hack typechecker不仅能识别类的字符串表示,而且还提供了提供类本身的语义的新类型。

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\CN;

<<__ConsistentConstruct>>
interface I {
  abstract const int A_CONST;
  public static function staticMeth(): void;
  public function meth(): void;
}

class C implements I {
  const int A_CONST = 10;
  public static function staticMeth(): void { echo "staticMeth\n"; }
  public function meth(): void { echo "meth\n"; }
  public function methOnlyInC(): void { echo "methOnlyInC\n"; }
}
class D {}

// With the classname<T> built-in type alias, the typechecker can now
// understand all these constructs!
function check_classname(classname<I> $cls, mixed $value): void {
  $const = $cls::A_CONST; // typechecked!
  $cls::staticMeth(); // typechecked!
  invariant($value instanceof $cls, "Bad if not");
  $value->meth(); // typechecked!
}

function ts_classname(): void {
  $c = new C();
  $d = new D();
  check_classname(C::class, $c);
  check_classname('C', $c); // error! only C::class is a classname
  check_classname(D::class, $d); // error! a D is not an I
}

ts_classname();

Output

staticMeth
meth

Fatal error: Class undefined: C in /data/users/joelm/user-documentation/guides/hack/20-types/02-type-system-examples/classname.php.type-errors on line 23

形状

形状是表示结构化数组的特定类型别名,具有确定性名称和键类型。它们也可以用作类型注释。

<?hh

namespace Hack\UserDocumentation\Types\TypeSystem\Examples\Shp;

type customer = shape('id' => int, 'name' => string);

function create_user(int $id, string $name): customer {
  return shape('id' => $id, 'name' => $name);
}

function ts_shape(): void {
  $c = create_user(0, "James");
  var_dump($c['id']);
  var_dump($c['name']);
}

ts_shape();

Output

int(0)
string(5) "James"


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

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号