集合
107
$vector = Vector {'one', 'two'};
list
($one, $two) = $vector;
$map = Map {1 => 'one', 0 => 'zero'};
list
($zero, $one) = $map;
// $zero
'zero'
$one
'one'
不可变集合
Vector
Map
以及
Set
都分别有其不可变的等价版本
ImmVector
ImmMap
ImmSet
。(
Pair
本身就是不可变的,它没有可变的情况。)它们不实现任何可以修改它们内容的方法,
并且它们也不能通过方括号语法或者
unset
进行修改。如果你试图这样做,将会抛出一
个“非法操作异常(
InvalidOperationException
)”。不可变集合的内容在它们被创
建的时候就固定下来。它们可以通过字面量语法进行创建,仅使用
ImmVector
ImmMap
或者
ImmSet
作为类名,或者通过它们的构造器,或者从另外的
collection
进行转换(参
5.4.4
节的相关内容)。
通常来说,如果可能的话你应该尽量使用不可变集合。如果一些数据不应被修改,那么
使用不可变集合就可以减少一个可能的
bug
来源。在类型系统中,它还会对程序行为编
码更多的信息,这总是一件好事情。
5.4
集合类型标注
大多数时间下,你不应该在类型标注中直接使用集合类名称本身。
Hack
提供了一系列接
口用于描述集合功能的元素,所以你应该在日常类型标注的时候使用这些接口。
例如,如果你正在编写一个函数,这个函数取一组值作为实参,并且不对它们进行修改,
那么相比较
Set
而言,你更应该标注这个实参为
ConstSet
。它是一个接口,一个可继承
的实体类。这增强了表现力,可以帮助类型检查器捕获更多的错误:如果你试图对函数
中的这个
Set
进行修改,将会有个类型错误。这同时使得函数定义对调用者更加清晰:
它需要一个集合,并且不会修改。
在本节的学习中,你将看到最有可能使用的接口。这将是以非常自然的方式来展示集合
类的面对对象接口。如果你只是想集中地查看集合类的
API
那么可以直接跳到
5.4.4
节。
那一小节没有对方法的解释信息,但是它们中的大多数都可以从字面意思上理解它的功
能,特别是那些类型标注。
5.4.1
核心接口
集合的核心接口有:
108
5
Traversable<T>
任何能够使用“不使用键的
foreach
”进行迭代的都是
Traversable
。在这样的
foreach
循环中,迭代变量的类型
T
。这是
Traversable
唯一能保证的事情,它并
没有声明任何方法。
Traversable
最重要的事情是:常规的
PHP
数组是
Traversable
的。这有些反
,因为通常来说只有对象可以实现接口,但
array
并不是对象。对于这种行为
Traversable
在运行环境中是个特例。
作为
array
collection
的补充,
Traversable
包含实现
Iterator
的对象。
Traversable
能够帮助弥补
array
collection
之间的差距。如果你对一个函数的实
参(不管是一个
array
,还是一个
collection
,或者其他)唯一要做的事情就是以不使
key
foreach
进行迭代,你可以考虑把它标注为
Traversable
值得注意的是,如果你想实现一个自己的类,可以在代码编程时使用
foreach
的类,
你不应该让它实现
Traversable
。请使用
Iterable
作为替代(稍后将进行描述)。
KeyedTraversable<Tk, Tv> extends Traversable<Tv>
KeyedTraversable
Traversable
很类似,但是它特别指明了在
foreach
语句中包
含一个
key
是合法的。常规
PHP
的数组就是
KeyedTraversable
。接下来的示例将
向我们展示
Traversable
KeyedTraversable
的区别:
function
notKeyed(Traversable<T> $traversable): void {
//
不合法
foreach
($traversable as $key => $value) {
// ...
}
}
function
keyed(KeyedTraversable<Tk, Tv> $traversable): void {
//
合法
foreach
($traversable as $key => $value) {
//
$key
的类型是
Tk
//
$value
的类型是
Tv
}
}
Container<T> extends Traversable<T>
Container
Traversable
非常相似,但是它并不包含实现
Iterator
的对象。换
句话说,它只包含数组和集合类实例。你使用
Container
唯一能做的事情就是以
foreach
进行
迭代。
KeyedContainer<Tk, Tv> extends KeyedTraversable<Tk, Tv>
KeyedContainer
KeyedTraversable
类似,但是
KeyedContainer
限制到数组
和集合类,而不是
Set
ImmSet
集合
109
Indexish<Tk, Tv> extends KeyedTraversable<Tk, Tv>
Indexish
表示能够使用方括号语法进行索引的任何变
$indexish[$key]
它没有
声明任何方法。就像
Traversable
KeyedTraversable
样,它是一个特殊的接口,
被数组、集合和其他支持此语法的对象“实现”的接口。
IteratorAggregate<T> extends Traversable<T>
这个接口是为了一些对象而存在的。这些对象都能够产生一个
Iterator
对象,用于
它们的内容迭代。和前边的三种接口不同的是,它并没有被
array
实现。在变量标注
中,你也不可能使用
IteratorAggregate
Iterable
或者
Traversable
可能是更合
适的选择。
IteratorAggregate
接口声明了一个单独的方法:
getIterator()
Iterator<T>
返回一个在对象内容上的
Iterator
。这个
Iterator
接口就是标准
PHP
中的那个接口。
Iterable<T> extends IteratorAggregate<T>
这里才是集合真正展示能力的地方。
Iterable
接口声明了很多方法:
toArray(): array
把集合转化为数组。注意返回值并没有类型实参,这仅仅是
个简单的
array
而不
array<T>
t
oValuesArray(): array
,把集合转化为丢弃
key
的数组,把对应的
key
依次用
0
n
1
的整数代替。
toVector(): Vector<T>
转化集合到一个
Vector
。这
toValuesArray()
常相似;如果对应的集合有
key
存在(例如这个集合是个
Map
),这些
key
将会
被丢弃。
toImmVector(): ImmVector<T>
,转化为一个不可变
Vector
toSet(): Set<T>
,把集合转化为一个
Set
丢弃所有可能存在的
key
toImmSet(): ImmSet<T>
,转化为一个不可变的
Set
values(): Iterable<T>
,返
Iterable
对象。它产生自集合的值(舍弃了键)
map<Tm>(function(T): Tm $callback): Iterable<Tm>
,把一个集合传入指
定函数后,经函数处理后的集合值组成
Iterable
对象进行返回。这和标准
PHP
array_map()
函数非常类似。这里有个把一个
Vector
的元素对象都乘以
10
的例子:
$nums = Vector {1, 2, 3};
print_r($nums->map(
function
($x) {
return
$x * 10; }));
HH\Vector Object
(
[0] => 10
110
5
[1] => 20
[2] => 30
)
filter(function(T): bool $callback): Iterable<T>
将一个集合传递进入
一个给定函数,所有可以使该函数返回
true
的对应集合的
value
将组成用于
返回值的
Iterable
对象。这里有个例子可以从一个
Vector
选出所有的偶数
$nums = Vector {1, 2, 3, 4};
print_r($nums->filter(function($x) { return $x % 2 === 0; }));
HH\Vector Object
(
[0] => 2
[1] => 4
)
zip<Tz>(Traversable<Tz> $traversable): Iterable<Pair<T, Tz>>
将返
回一个
Iterable
对象,它将“该集合
collection
中的
value
”和“来自传入的
Traversable
value
”配对。下面的例子是对这个函数进行解释的最好方法:
$english = Vector {'one', 'two', 'three'};
$french = Vector {'un', 'deux', 'trois'};
print_r($english->zip($french));
这将会输出:
HH\Vector Object
(
[0] => HH\Pair Object
(
[0] => one
[1] => un
)
[1] => HH\Pair Object
(
[0] => two
[1] => deux
)
[2] => HH\Pair Object
(
[0] => three
[1] => trois
)
)
如果两个集合有着不同的成员计数值,那么所得到的
Iterable
将会和更小的计数
值一致。
集合
111
KeyedIterable<Tk, Tv> extends Iterable<Tv>
这个和
Iterable
类似,但是它包含
key
的类型。它添加了一些新的方法,并且以不同
的返回类型对
Iterable
中的一些方法进行了覆盖。新增加的方法被列举在前面:
toKeysArray(): array
返回一个由
Iterable
的键组成的数组。
toMap()
Map<Tk, Tv>
返回一个由
Iterable
转化成的
Map
keys()
Iterable<Tk> *
返回一个
Iterable
上面的所有
key
组成的
Iterable
mapWithKey<Tm>(function(Tk, Tv): Tm $callback)
KeyedIterable<Tk,
Tm>
map()
似,但是会把键和值一样传回调入回调函数。
filterWithKey(function(Tk, Tv): bool $callback)
KeyedIterable<Tk,
Tv>
filter()
类似,但是会把
key
value
一样传入回调函数。
getIterator(): KeyedIterator<Tk, Tv>
是一个拥有更特殊返回类型的覆盖方
法。
map<Tm>(function(T): Tm $callback)
KeyedIterable<Tk, Tu>
是一个拥有
更特殊返回类型的覆盖方法。
filter(function(T): bool $callback)
Iterable<T>
是一个拥有更特殊返回
类型的覆盖方法。
zip<Tz>(Traversable<Tz> $traversable)
Iterable<Pair<T, Tz>>
是一个拥
有更特殊返回类型的覆盖方法。
5.4.2
常见集合接口
这里有三大核心接口,用于声明最基本的集合函数功能。在类型标注方面,你基本上不
会用到它们,因为它们并没有什么特别的用途。我们在这里将学习它们的核心函数:
ConstCollection<T>
一个关于类型
T
值的只读集合。它和以下几方面无关:值的唯一性、顺序、基础实
现或其他。
每个具体的集合类都间接地实现这个接口。这个接口看起来和
Map
不搭配,因为它
只有一个类型形参,而
Map
需要两个(一个用于
key
的类型,另外一个用于
value
类型),但是
Map
确实实现了
ConstCollection
接口:一个
Map
拥有
key
类型
Tk
value
类型
Tv
,实现了
ConstCollection<Pair<Tk,Tv>>
这个接口声明了三个方法:

Get Hack 与HHVM 权威指南 now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.