hack集合:语义
一般来说,Hack系列应该是您决定使用新代码时的第一选择。它们提供了所需的可读性,性能和类型可检测性,而不会在灵活性方面牺牲很多。
也就是说,有一个关键领域,你必须认识到集合和数组之间的差异。
参考语义
Hack集合有参考语义。这意味着集合被视为对象,对集合进行的修改会影响已分配或以某种方式复制的集合。
数组有价值语义。因此,对数组的修改将不会对已分配或以某种方式复制到其中的数组造成影响。
<?hh
namespace Hack\UserDocumentation\Collections\Semantics\Examples\RefVal;
function foo(Vector<int> $vec): void {
$vec[1] = 500;
var_dump($vec);
}
function bar(array<int> $arr): void {
$arr[1] = 500;
var_dump($arr);
}
function reference_semantics(): void {
$vec = Vector {1, 2, 3};
var_dump($vec);
$cp_vec = $vec;
var_dump($cp_vec); // The two vectors are the same reference
$vec[0] = 100; // $cp_vec is also affected by the change. They are the same.
var_dump($vec);
var_dump($cp_vec);
foo($vec); // $vec will be affected by anything foo does to it.
var_dump($vec);
}
function value_semantics(): void {
$arr = array (1, 2, 3);
var_dump($arr);
$cp_arr = $arr;
var_dump($cp_arr); // The two arrays have the same values, but are copies.
$arr[0] = 100; // $cp_arr is not affected by this
var_dump($arr);
var_dump($cp_arr);
bar($arr); // $arr is not affected by anytnig bar does to it
var_dump($arr);
}
function run(): void {
echo "--- REFERENCE SEMANTICS ---\n\n";
reference_semantics();
echo "\n\n--- VALUE SEMANTICS ---\n\n";
value_semantics();
}
run();
Output
--- REFERENCE SEMANTICS ---
object(HH\Vector)#1 (3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
object(HH\Vector)#1 (3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
object(HH\Vector)#1 (3) {
[0]=>
int(100)
[1]=>
int(2)
[2]=>
int(3)
}
object(HH\Vector)#1 (3) {
[0]=>
int(100)
[1]=>
int(2)
[2]=>
int(3)
}
object(HH\Vector)#1 (3) {
[0]=>
int(100)
[1]=>
int(500)
[2]=>
int(3)
}
object(HH\Vector)#1 (3) {
[0]=>
int(100)
[1]=>
int(500)
[2]=>
int(3)
}
--- VALUE SEMANTICS ---
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
array(3) {
[0]=>
int(100)
[1]=>
int(2)
[2]=>
int(3)
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
array(3) {
[0]=>
int(100)
[1]=>
int(500)
[2]=>
int(3)
}
array(3) {
[0]=>
int(100)
[1]=>
int(2)
[2]=>
int(3)
}
上面的例子显示了引用和值语义之间的区别。这在函数调用中也是如此。
将数组转换为集合
使用数组将现有代码转换为集合时,数组具有值语义和集合的引用语义实际上非常重要。
<?hh
namespace Hack\UserDocumentation\Collections\Semantics\Examples\Converting;
function foo_with_vector(Vector<int> $vec): void {
$vec[] = 5;
}
function foo_with_array(array<int> $arr): void {
$arr[] = 5;
}
function run(): void {
$arr = array (1, 2, 3);
foo_with_array($arr);
$arr[] = 4; // The call to foo_with_array did not affect this $arr.
var_dump($arr);
// Many would expect the same sequence of code to work the same
$vec = Vector {1, 2, 3};
foo_with_vector($vec);
$vec[] = 4; // Uh oh, reference semantics at work. foo_with_vector affects us
var_dump($vec);
}
run();
Output
array(4) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(4)
}
object(HH\Vector)#1 (5) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(5)
[4]=>
int(4)
}
所以,如果你有一些自动代码修改转换array到Vector,你的代码可以打破由上面的例子所示。
帮助解决这个问题的一种方法是使用ImmVector并Vector::immutable()确保在将其传递给函数时不能修改集合。
<?hh
namespace Hack\UserDocumentation\Collections\Semantics\Examples\ConvertingImm;
function foo_with_vector(ImmVector<int> $vec): void {
try {
// The type checker actually won't allow this, but you can run this
// and catch the exception
$vec[] = 5;
} catch (\InvalidOperationException $ex) {
echo "Cannot modify immutable collection. Create copy first\n";
$cp_vec = new Vector($vec);
$cp_vec[] = 5;
var_dump($cp_vec);
}
}
function foo_with_array(array<int> $arr): void {
$arr[] = 5;
}
function run(): void {
$arr = array (1, 2, 3);
foo_with_array($arr);
$arr[] = 4; // The call to foo_with_array did not affect this $arr.
var_dump($arr);
$vec = Vector {1, 2, 3};
// Now any change in foo_with_vector won't affect this $vec
foo_with_vector($vec->immutable());
$vec[] = 4;
var_dump($vec);
}
run();
Output
array(4) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(4)
}
Cannot modify immutable collection. Create copy first
object(HH\Vector)#4 (4) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(5)
}
object(HH\Vector)#1 (4) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(4)
}
Equality on Collections ==
集合可以比较平等
$coll1 == $coll2;
这是规则:
- 两个集合是否是相同类型的集合(忽略可变性)?如果没有,那么平等就是false。
- 这两个集合是否具有相同数量的值(或地图的键)?如果没有,那么平等就是false。
- 对于向量和对,每个索引的值是否等于==?如果没有,那么平等false; 否则,平等就是true。
- 对于集合,一个中的每个值都包含在另一个中?如果没有,那么平等false; 否则,平等就是true。
- 对于地图,每个密钥是否存在于另一个中===?如果没有,那么平等就是false。如果是,相同的按键映射到每个集合中的相等值==?如果没有,那么平等false; 否则平等是true。
<?hh
namespace Hack\UserDocumentation\Collections\Semantics\Examples\Equality;
function run(): void {
$vecA = Vector {1, 2, 3};
$vecB = Vector {1, 2, 3};
$vecC = Vector {4, 5, 6};
$vecD = Vector {2, 1, 3};
$setA = Set {1, 2, 3};
$setB = Set {3, 2, 1};
$mapA = Map {1 => 'A', 2 => 'B'};
$mapB = Map {2 => 'B', 1 => 'A'};
var_dump($vecA == $vecB); // true
var_dump($vecA == $vecC); // false, different values
var_dump($vecA == $vecD); // false, same values, but different order
var_dump($setA == $setB); // true, same values, order doesn't matter
var_dump($mapA == $mapB); // true, ordering of keys doesn't matter
}
run();
Output
bool(true)
bool(false)
bool(false)
bool(true)
bool(true)
Identity on Collections ===
集合可以进行身份比较。
$ coll1 === $ coll2;
只有身份才能评估true两个集合是否是相同的对象。否则的话false。
<?hh
namespace Hack\UserDocumentation\Collections\Semantics\Examples\Identity;
function run(): void {
$vecA = Vector {1, 2, 3};
$vecB = Vector {1, 2, 3};
$vecC = $vecA;
$setA = Set {1, 2, 3};
$setB = Set {3, 2, 1};
$setC = $setB;
$mapA = Map {1 => 'A', 2 => 'B'};
$mapB = Map {2 => 'B', 1 => 'A'};
var_dump($vecA === $vecB); // false, not the same object
var_dump($vecA === $vecC); // true, the same object
var_dump($setA === $setB); // false, not the same object
var_dump($setA === $setC); // false, not the same object
var_dump($setB === $setC); // true, the same object
var_dump($mapA === $mapB); // false, not the same object
}
run();
Output
bool(false)
bool(true)
bool(false)
bool(false)
bool(true)
bool(false)
运用 list()
您可以使用 list()和 Vector和Pair一样可以使用数组。
虽然你可以使用list()与Map和Set在运行时,hack typechecker将抛出一个错误。请注意,您必须有一个零整数键和随后的有序键为Map和Set; 否则你会得到一个OutOfBoundsException。
<?hh
namespace Hack\UserDocumentation\Collections\Semantics\Examples\Liust;
function run(): void {
$vecA = Vector {1, 2, 3};
$setA = Set {0, 1, 2};
$mapA = Map {1 => 'A', 0 => 'B'};
$pairA = Pair {999, 9999};
$setB = Set {200, 300};
list($v1, $v2, $v3) = $vecA;
list($s1, $s2, $s3) = $setA;
list($m1, $m2) = $mapA;
list($p1, $p2) = $pairA;
try {
// Exception will be thrown since there is no 0 and 1 value in the Set
// to serve as a key-like value
list($x, $y) = $setB;
} catch (\OutOfBoundsException $ex) {
var_dump($ex->getMessage());
}
var_dump($v1);
var_dump($v2);
var_dump($v3);
var_dump($s1);
var_dump($s2);
var_dump($s3);
var_dump($m1);
var_dump($m2);
var_dump($p1);
var_dump($p2);
}
run();
Output
string(28) "Integer key 1 is not defined"
int(1)
int(2)
int(3)
int(0)
int(1)
int(2)
string(1) "B"
string(1) "A"
int(999)
int(9999)
使用阵列内置函数
Hack集合支持一些内置的数组函数。
Sorting
方法 | 有效集合 |
---|---|
sort() | Vector |
rsort() | Vector |
usort() | Vector |
asort() | Map |
arsort() | Map |
ksort() | Map |
krsort() | Map |
uasort() | Map |
uksort() | Map |
natsort() | Map |
natcasesort() | Map |
Pairs不支持排序,因为它们是不可变的。您可以将其转换Pair为可变集合,然后进行排序。
目前,与排序Sets的Hack类型检查器和HHVM不同意的矛盾是矛盾的。例如,typechecker可以调用sort()一个Set,但HHVM不是; 和HHVM都确定了要在通话asort()上Set,但typechecker是否定的。我们正在努力解决这个问题。
Querying
方法 | 有效集合 |
---|---|
array_keys() | 所有 |
array_key_exists() | 所有 |
array_values() | 所有 |
count() | 所有 |
idx() | Vector , Map |
idx()
是一个函数,如果没有找到索引(null
如果没有指定索引)则返回一个collection,index和一个可选的默认返回值。
Manipulation
方法 | 有效集合 |
---|---|
array_combine() | 所有 |
array_diff() | 所有 |
array_diff_key() | 所有 |
array_filter | 所有 |
array_intersect() | 所有 |
array_intersect_key() | 所有 |
array_map() | 所有 |
implode() | 所有 |
serialize() | 所有 |
Modification
方法 | 有效集合 |
---|---|
array_pop() | 所有 |
array_push() | 所有 |
array_shift() | Vector , Set |
array_unshift | Vector , Set |
Introspection
方法 | 有效集合 |
---|---|
var_dump() | 所有 |
print_r() | 所有 |
var_export() | 所有 |
debug_zval_dump | 所有 |
APC
方法 | 有效集合 |
---|---|
apc_store() | 所有 |
扩展
所有具体的收集类都是final(即它们不能被分类)。但是,您可以从集合基础架构提供的各种接口创建新的具体集合类。