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)

現(xiàn)在想象一下,你意識到,有時用戶的ID可能是一個string以及一個int。但是你知道具體的類User將確切地知道將返回什么類型。

泛型引入了類型參數(shù)的概念,它基本上允許您將類型占位符關(guān)聯(lián)到一個類或方法,然后在類實例化或方法被調(diào)用后,該類就可以完全關(guān)聯(lián)。

<?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"

請注意,我們必須傳播一個類型參數(shù)的附加到類本身和所有擴展它?,F(xiàn)在想想,如果我們有成百上千個使用特征和接口的地方,我們也必須更新它們。

在類型參數(shù)化方面,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并使用關(guān)鍵字type。您可以為常量指定一個名稱,以及必須遵守的任何可能的約束條件。有關(guān)語法的信息,請參閱下文。

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

類型常量有點類似于抽象方法,其中基類定義了方法簽名而沒有實體,而子類提供了實際的實現(xiàn)。

語法

類型常量的語法取決于您是處于抽象類還是具體類。

抽象類

在抽象類中,語法是最簡單的:

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
}

使用類型常量

假設(shè)類型常量是類的一個常數(shù),那么可以使用它來引用它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)

其他規(guī)則

還有一些關(guān)于類型常量的其他規(guī)則:

  • 像類型常量一樣,類型常量具有public可見性。
  • 在聲明類型常量的直接類層次結(jié)構(gòu)之外,可以通過classname::typeConstantName(例如Foo::T)引用它們。
  • 像泛型一樣,類型常量只能用在類型注釋中。它們不能用于其他語言結(jié)構(gòu),比如new,instanceof()。
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號