hack类型系统
在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 / array
/ tuple
。
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"