本节书摘来自华章出版社《Hack与HHVM权威指南》一书中的第2章,第2.2节,作者 Owen Yamauchi,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
类并不是唯一可以被泛型化的实体。
泛型函数在它的名字和参数列表的在圆括号之间有类型形参的列表。并且它可以像往常一样被调用,请看下面的例子:
function wrap<T>(T $value): Wrapper<T> { return new Wrapper($value); } function main(): void { $w = wrap(20); }就像这个例子所示 ,泛型函数的类型形参能够在函数的形参类型和返回类型这两个地方使用。方法也可以是泛型的。如果一个方法存在于一个泛型类或者trait中,它能够使用其闭合类的类型形参,如下所示:
class Logger { public function logWrapped<Tval>(Wrapper<Tval> $value): void { // ... } } class Processor<Tconfig> { public function checkValue<Tval>(Tconfig $config, Tval $value): bool { // ... } }trait和接口两者都可以是泛型的。语法和泛型类的语法非常相似,都是在名称后面放置类型形参的列表:
trait DebugLogging<Tval> { public static function debugLog(Tval $value): void { // ... } } interface WorkItem<Tresult> { public function performWork(): Tresult; } 任何使用一个泛型trait或者实现一个泛型接口的代码,都必须特别指明相关的类型实参: class StringProducingWorkItem implements WorkItem<string> { use DebugLogging<string>; // ... }泛型类可以传递它的类型形参到它所实现的接口类型或使用的trait中。
class ConcreteWorkItem<Tresult> implements WorkItem<Tresult> { use DebugLogging<Tresult>; // ... }关于类型别名的详细内容请参见3.2节的内容。它们能够通过立即添加类型形参列表到它们别名后而完成泛型化。type matrix<T> = array<array<T>>;这里有一个关于类型别名的非常有意思的泛型程序,在这个程序中,你将不会在右边的位置上使用类型形参。最好的示例就是序列化:
newtype serialized<T> = string; function typed_serialize<T>(T $value): serialized<T> { return serialize($value); } function typed_unserialize<T>(serialized<T> $value): T { return unserialize($value); }鉴于普通的没有类型声明的serialize() API会丢失有关序列化值类型的相关信息,这个别名使类型检查器在大量类型的序列化版本中可以进行分辨。这在类型检查器中并不会引发错误,这是因为本质上来说,类型检查器并没有对它进行检查:unserialize()函数没有返回类型标注,所以类型检查器只是简单地相信它所做的事情,并且相关返回值也是正确的(请参见1.4.2节)。在这里,类型检查器知道$unserialized变量是字符串类型。
$serialized_str = typed_serialize("hi"); $unserialized = typed_unserialize($serialized_str);你还可以对每个序列化的值类型进行检查,以保证相关的变量类型。
function process_names(serialized<array<string>> $arr): void { foreach (typed_unserialize($arr) as $name) { // 这里的$name已被确认为字符串