hack类型常量:简介

2018-11-30 11:30 更新

想象一下,你有一个非泛型的类,并有一些不同的extends类。

<?hh

namespace Hack\UserDocumentation\TypeConstants\Intro\Examples\NonParameterized;

abstract class User {
  public function __construct(private int $id) {}
  public function getID(): int {
    return $this->id;
  }
}

trait UserTrait {
  require extends User;
}

interface IUser {
  require extends User;
}

class AppUser extends User implements IUser {
  use UserTrait;
}

function run(): void {
  $au = new AppUser(-1);
  var_dump($au->getID());
}

run();

Output

int(-1)

现在想象一下,你意识到,有时用户的ID可能是一个string以及一个int。但是你知道具体的类User将确切地知道将返回什么类型。

泛型引入了类型参数的概念,它基本上允许您将类型占位符关联到一个类或方法,然后在类实例化或方法被调用后,该类就可以完全关联。

<?hh

namespace Hack\UserDocumentation\TypeConstants\Intro\Examples\Generics;

abstract class User<T as arraykey> {
  public function __construct(private T $id) {}
  public function getID(): T {
    return $this->id;
  }
}

trait UserTrait<T as arraykey> {
  require extends User<T>;
}

interface IUser<T as arraykey> {
  require extends User<T>;
}

// We know that AppUser will only have int ids
class AppUser extends User<int> implements IUser<int> {
  use UserTrait<int>;
}

class WebUser extends User<string> implements IUser<string> {
  use UserTrait<string>;
}

class OtherUser extends User<arraykey> implements IUser<arraykey> {
  use UserTrait<arraykey>;
}

function run(): void {
  $au = new AppUser(-1);
  var_dump($au->getID());
  $wu = new WebUser('-1');
  var_dump($wu->getID());
  $ou1 = new OtherUser(-1);
  var_dump($ou1->getID());
  $ou2 = new OtherUser('-1');
  var_dump($ou2->getID());
}

run();

Output

int(-1)
string(2) "-1"
int(-1)
string(2) "-1"

请注意,我们必须传播一个类型参数的附加到类本身和所有扩展它。现在想想,如果我们有成百上千个使用特征和接口的地方,我们也必须更新它们。

在类型参数化方面,Hack引入了泛型称为类型常量的替代特性。类型常量允许将类型声明为类成员常量,而不是直接在类本身声明的类型。

<?hh

namespace Hack\UserDocumentation\TypeConstants\Intro\Examples\TypeConstants;

abstract class User {
  abstract const type T as arraykey;
  public function __construct(private this::T $id) {}
  public function getID(): this::T {
    return $this->id;
  }
}

trait UserTrait {
  require extends User;
}

interface IUser {
  require extends User;
}

// We know that AppUser will only have int ids
class AppUser extends User implements IUser {
  const type T = int;
  use UserTrait;
}

class WebUser extends User implements IUser {
  const type T = string;
  use UserTrait;
}

class OtherUser extends User implements IUser {
  const type T = arraykey;
  use UserTrait;
}

function run(): void {
  $au = new AppUser(-1);
  var_dump($au->getID());
  $wu = new WebUser('-1');
  var_dump($wu->getID());
  $ou1 = new OtherUser(-1);
  var_dump($ou1->getID());
  $ou2 = new OtherUser('-1');
  var_dump($ou2->getID());
}

run();

Output

int(-1)
string(2) "-1"
int(-1)
string(2) "-1"

注意语法abstract const type <name> [ as <constraint> ];。所有类型的常量是,const并使用关键字type。您可以为常量指定一个名称,以及必须遵守的任何可能的约束条件。有关语法的信息,请参阅下文。

还要注意,只有类本身和直接的孩子需要更新新的类型信息。

类型常量有点类似于抽象方法,其中基类定义了方法签名而没有实体,而子类提供了实际的实现。

语法

类型常量的语法取决于您是处于抽象类还是具体类。

抽象类

在抽象类中,语法是最简单的:

abstract const type <name> [as <constraint>]; // constraint optional

例如:

abstract class A {
  abstract const type Foo;
  abstract const type Bar as arraykey;
}

那么在这个具体的子类中:

class C extends A {
  const type Foo = string;
  // Has to be int or string since was constrained to arraykey
  const type Bar = int;
}

具体类

你可以在具体类中声明一个类型常量,但是它需要不同的语法:

const type <name> [as <constraint>] = <type>; // constraint optional

例如:

class NoChild {
  const type Foo = ?string;
}

class Parent {
  const type Foo as arraykey = arraykey; // need constraint for child override
}

class Child extends Parent {
  const type Foo = string; // a string is an arraykey, so ok
}

使用类型常量

假设类型常量是类的一个常数,那么可以使用它来引用它this。作为一个类型注释,你注释一个类型常量,如:

this::<name>

例如,

this::T

你可以this::用类似的方式来考虑this返回类型

这个例子显示了类型常量的真正好处。该属性被定义Base,但可以有不同的类型,取决于它在哪里使用的上下文。

<?hh

namespace Hack\UserDocumentation\TypeConstants\Introduction\Examples\Annotate;

abstract class Base {
  abstract const type T;
  protected this::T $value;
}

class Stringy extends Base {
  const type T = string;
  public function __construct() {
    // inherits $value in Base which is now setting T as a string
    $this->value = "Hi";
  }
  public function getString(): string {
    return $this->value; // property of type string
  }
}

class Inty extends Base {
  const type T = int;
  public function __construct() {
    // inherits $value in Base which is now setting T as an int
    $this->value = 4;
  }
  public function getInt(): int {
    return $this->value;  // property of type int
  }
}

function run(): void {
  $s = new Stringy();
  $i = new Inty();
  var_dump($s->getString());
  var_dump($i->getInt());
}

run();

Output

string(2) "Hi"
int(4)

其他规则

还有一些关于类型常量的其他规则:

  • 像类型常量一样,类型常量具有public可见性。
  • 在声明类型常量的直接类层次结构之外,可以通过classname::typeConstantName(例如Foo::T)引用它们。
  • 像泛型一样,类型常量只能用在类型注释中。它们不能用于其他语言结构,比如new,instanceof()。
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号