codecamp

hack泛型:字符实体

泛型可以用于你习惯于用Hack编程的许多实体,包括类,函数,方法,接口,类型别名和特性。

Classes

考虑下面的例子,其中Stack是一个具有一个类型参数的泛型类T:

<?hh

namespace Hack\UserDocumentation\Generics\Entities\Examples\Classes;

class StackUnderflowException extends \Exception {}

class Stack<T> {
  private array<T> $stack;
  private int $stackPtr;

  public function __construct() {
    $this->stackPtr = 0;
    $this->stack = array();
  }

  public function push(T $value): void {
    $this->stack[$this->stackPtr++] = $value;
  }

  public function pop(): T {
    if ($this->stackPtr > 0) {
      return $this->stack[--$this->stackPtr];
    } else {
      throw new StackUnderflowException();
    }
  }
}

function useIntStack(Stack<int> $stInt): void {
  $stInt->push(10);
  $stInt->push(20);
  $stInt->push(30);
  echo 'pop => ' . $stInt->pop() . "\n";
  $stInt->push(10.5); // rejected as not being type-safe
  echo 'pop => ' . $stInt->pop() . "\n";
}

function run(): void {
  $s = new Stack();
  $s->push(5);
  useIntStack($s);
}

run();

Output

pop => 30
pop => 10.5

如图所示,type参数T用于实例属性声明中$stack,作为实例方法的参数类型push,以及作为实例方法的返回类型pop。请注意,虽然push并pop使用类型参数,但它们本身不是通用方法。

该行$stInt->push(10.5);尝试push使用非int参数进行调用。这是被拒绝的,因为$stInt是一堆int,我们正试图推一个float。

功能

下面是一个泛型函数的例子maxVal,有一个类型参数T:

<?hh

namespace Hack\UserDocumentation\Generics\Entities\Examples\Functions;

function maxVal<T>(T $p1, T $p2): T {
  return $p1 > $p2 ? $p1 : $p2;
}

function run(): void {
  var_dump(maxVal(10, 20));
  var_dump(maxVal(15.6, -20.78));
  var_dump(maxVal('red', 'green'));
}

run();

Output

int(20)
float(15.6)
string(3) "red"

该函数返回传递给它的两个参数中较大的一个。在调用的情况下maxVal(10, 20),如果这两个参数的类型都是int,则推断为与类型参数对应的类型T,并int返回一个值。在呼叫的情况下maxVal(15.6, -20.78),T被推断为float,而在maxVal('red', 'green'),T被推断为string。

方法

虽然push和pop方法在Stack上面的类示例中的泛型类中定义,但它们本身并不是泛型的。它们已经绑定到类类型参数T。

就像泛型函数一样,泛型方法也有自己的类型参数,即使该方法不属于泛型类。考虑库类型Pair:

final class Pair<Tv1, Tv2> implements ConstVector<mixed> {
  // …
  public function map<Tu>( (function(Tv): Tu) $callback ): Vector<Tu>
  public function zip<Tu>(Traversable<Tu> $iter): Vector<Pair<mixed, Tu>>
  public function zip<Tu>( Traversable<Tu> $iterable ): Vector<Pair<mixed, Tu>>
}

正如我们所看到的,方法map和zip每个方法都有一个通用参数Tu,它的类型是从传递给每个方法的参数中推断出来的。这个泛型参数意味着我们可以在方法的参数或返回类型中使用它。请注意,泛型方法具有与类不同的类型参数(例如Tvvs Tu)。如果绑定了这些方法Tv,那么我们就不需要方法的泛型参数,因为它已经绑定到了类的类型参数。

接口

像一个类一样,一个接口可以有类型参数; 例如:

<?hh

namespace Hack\UserDocumentation\Generics\Entities\Examples\Interfaces;

interface MyCollection<T> {
  public function put(T $item): void;
  public function get(): ?T;
}

class MyStack<T> implements MyCollection<T> {
  private Vector<T> $storage;

  public function __construct() {
    $this->storage = Vector {};
  }

  public function put(T $item): void {
    $this->storage[] = $item;
  }
  public function get(): ?T {
    // LIFO
    return $this->storage->count() > 0 ? $this->storage[0] : null;
  }
}

class MyQueue<T> implements MyCollection<T> {
  private Vector<T> $storage;

  public function __construct() {
    $this->storage = Vector {};
  }

  public function put(T $item): void {
    $this->storage[] = $item;
  }
  public function get(): ?T {
    // FIFO
    return $this->storage->count() > 0
      ? $this->storage[$this->storage->count() - 1]
      : null;
  }
}

function processCollection<T>(MyCollection<T> $p1): void {
  var_dump($p1->get());
}

function run(): void {
  $s = new MyStack();
  $s->put(5);
  $s->put(9);
  $s->put(3);
  processCollection($s);
  $q = new MyQueue();
  $q->put(5);
  $q->put(9);
  $q->put(3);
  processCollection($q);
}

run();

Output

int(5)
int(3)

在这里,我们有通用的堆栈和队列类,每个类都实现相同的通用接口,使这些类能够以一致的方式存储和检索元素。

Traits

像通用类一样,通用特征具有类型参数列表; 例如:

trait MyTrait<T1, T2> {
  public static function f(T1 $value): void {
  // ...
}

类型别名

类型别名可以是任何类型的别名,包括泛型类型。例如:

newtype Matrix<T> = Vector<Vector<T>>;
type Serialized<T> = string;    // T is not used
hack泛型介绍
hack泛型:子类型
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }