本节书摘来自华章出版社《Hack与HHVM权威指南》一书中的第2章,第2.6节,作者 Owen Yamauchi,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
让我们回到关于Wrapper类的引导范例。类型检查器应该接受下面的代码吗?
function takes_wrapper_of_num(Wrapper<num> $w): void { // ... } function takes_wrapper_of_int(Wrapper<int> $w): void { takes_wrapper_of_num($w); }那么问题来了,传递一个整型的wrapper到一个期待值为num的wrapper的话,这个操作是非法的吗?看起来应该是这样的:int是num的亚型(意味着任何为int的值都会是个num),所以看起来Wrapper应该同样是Wrapper的亚型。事实上,对于这个例子,类型检查器将会报告一个错误。对于类型检查器来说,我们关于int和num的亚型关系传递给Wrapper和Wrapper之间亚型关系的假设是不成立的。为了说明其中的缘由,我们请思考下面的代码,函数takes_wrapper_of_num()能够做这个事情:
function takes_wrapper_of_num(Wrapper<num> $w): void { $w->setValue(3.14159); }对于它自己来说,这是合法的:设置一个Wrapper内部的值为一个float类型的值。但是如果你传递一个Wrapper到上述版本的函数takes_wrapper_of_num()中,这将会导致wrapper再也不是整型。所以类型检查器不接受对函数takes_wrapper_of_num()传递Wrapper。这并不是类型安全的。需要特别说明的是,这里有一项铁的规则:类型检查器并不思考函数takes_wrapper_of_num()实际上在做什么。即使函数takes_wrapper_of_num()是空的,类型检查器仍然会报告一个错误。现在,我们看另外一段代码,类型检查器应该接受这段代码吗?
function returns_wrapper_of_int(): Wrapper<int> { // ... } function returns_wrapper_of_num(): Wrapper<num> { return returns_wrapper_of_int(); }虽然这次直觉上看起来一切正常,但是类型检查器会再一次报告错误。原因是非常相似的。设想一下,我们在空白处填上如下内容:
function returns_wrapper_of_int(): Wrapper<int> { static $w = new Wrapper(20); return $w; } function returns_wrapper_of_num(): Wrapper<num> { return returns_wrapper_of_int(); } function main(): void { $wrapper_of_num = returns_wrapper_of_num(); $wrapper_of_num->setValue(2.71828); }这很明显是非法的——在main()函数执行后,任何对函数returns_wrapper_of_int()的调用都会返回某一个非int类型的wrapper。所以,再一次,对于函数returns_wrapper_of_num()里面的return语句,类型检查器会报告一个错误。数组和集合数组和Hack的不可变集合类(ImmVector、ImmMap、ImmSet和Pair)表现上是不一致的。举例来说,它们遵从着很直观的概念,array是array的亚型。下例中对数组的使用是合法的:
function takes_array_of_num(array<num> $arr): void { // ... } function takes_array_of_int(array<int> $arr): void { takes_array_of_num($arr);??// OK }类似的行为对于不可变集合类的值类型注3也是有效的。不必考虑你是使用它们自己的名字进行注解,还是用类似ConstVector这样的接口名字(这是推荐的)进行注解:
function takes_constvector_of_num(ConstVector<num> $cv): void { // ... } function takes_constvector_of_int(ConstVector<int> $cv): void { takes_constvector_of_num($cv);??// OK } function takes_constmap_of_arraykey_mixed(ConstMap<string, mixed> $cm): void { // ... } function takes_constmap_of_string_int(ConstMap<string, int> $cm): void { takes_constmap_of_arraykey_mixed($cm);??// OK }为什么这对于数组和不可变集合是合法的,而对于Wrapper却是非法的?就不可变集合来说,原因很简单:它们是不可变的。即使你传递一个ImmVector到参数为ImmVector的函数,这个函数也没有办法设置一个非整型值到vector中。这里没有任何方法可以破坏vector只能包含整数的规矩。就数组而言,道理也是一样的。出于相同的目的,由于值传递语义数组和不可变集合在表象上几乎一致。在上一个示例中,从takes_array_of_num()的视角,在函数takes_array_of_int()主体内的数组实际上是只读的。函数takes_array_of_num()不能够导致数组内部具有非整型值。因为它根本就无法访问原始数组,只有复制的权限。