为什么在Hack中不允许泛型实例化语法



From the docs:

注意:HHVM允许像$x = Vector<int>{5,10};这样的语法,但是Hack在这种情况下禁用语法,而选择推断它。

这有什么特别的原因吗?这不是违反了快速失败规则吗?

在某些情况下,这会导致错误被延迟,从而导致更困难的回溯。

例如:

<?hh // strict
function main() : void {
    $myVector = new Vector([]); // no generic syntax
    $myVector->addAll(require 'some_external_source.php');
}

上面的代码不会导致错误,直到它被用在静态类型集合实际存在的上下文中:

class Foo
{
    public ?Vector<int> $v;
}
$f = new Foo();
$f->v = $myVector;

现在,如果向量包含int以外的东西,则会出现错误。但是必须追溯错误,直到有缺陷的数据被实际导入的地方。如果可以首先使用泛型语法实例化vector,则不需要这样做:

$myVector = new Vector<int>([]);
$myVector->addAll(require 'some_external_source.php'); // fail immediately

我在Facebook从事Hack类型系统和类型检查工作。这个问题在facebook内部已经被问过几次了,最好有一个好看的、外部可见的地方把答案写下来。

首先,你的问题以以下代码为前提:

<?hh // strict
function main() : void {
    $myVector = new Vector([]); // no generic syntax
    $myVector->addAll(require 'some_external_source.php');
}

然而,由于在顶层之外使用require,该代码没有通过类型检查器,因此在HHVM上实际执行它的任何结果都是未定义的行为,使得整个讨论对该代码没有意义。

但是对于其他可能进行类型检查的代码来说,这仍然是一个合理的问题,所以让我继续回答这个问题。:)

不受支持的原因是,与许多其他语言不同,类型检查器实际上能够正确地推断泛型,因此我们做出了判断,认为语法会妨碍它,并决定不允许它。事实证明,如果你不担心,我们会推断正确,并且仍然给出有用的类型错误。当然,您可以设计出不像您希望的那样"快速失败"的人为代码,但它确实是人为的。以您的示例的以下修复为例:

<?hh // strict
function main(): void {
  $myVector = Vector {}; // I intend this to be a Vector<int>
  $myVector[] = 0;
  $myVector[] = 'oops'; // Oops! Now it's inferred to be a Vector<mixed>
}

您可能会认为这很糟糕,因为您打算有一个Vector<int>,但实际上有一个没有类型错误的Vector<mixed>;您可能希望在创建它时能够表达这一点,以便将'oops'添加到其中会导致这样的错误。但是没有类型错误只是因为您从未真正尝试使用$myVector !如果您试图取出它的任何值,或者从函数中返回它,就会得到某种类型兼容性错误。例如:

<?hh // strict
function main(): Vector<int> {
  $myVector = Vector {}; // I intend this to be a Vector<int>
  $myVector[] = 0;
  $myVector[] = 'oops'; // Oops! Now it's inferred to be a Vector<mixed>
  return $myVector; // Type error!
}

return语句将导致类型错误,指出'oops'是一个字符串,与int返回类型注释不兼容——这正是您想要的。所以这个推断是正确的,它是有效的,你实际上不需要显式地注释局部变量的类型。

但是如果你真的想的话,为什么不能呢?因为在实例化新对象时只注释泛型并不是真正正确的特性。你所说的"但偶尔我真的想注释Vector<int> {}"的核心实际上是"但偶尔我真的想注释locals"。因此,正确的语言特性不是让您编写$x = Vector<int> {};,而是让您显式声明变量并编写Vector<int> $x = Vector {};——这也允许编写int $x = 42;之类的东西。向语言中添加显式变量声明比仅仅在对象实例化时注释泛型更通用、更合理。(然而,这并不是一个正在积极开发的功能,我也不认为它会在近期或中期出现,所以现在不要抱太大希望。但是我们做这个决定的原因是要留有余地。

此外,允许这两种语法中的任何一种都会在此时产生误导。泛型仅由静态类型检查器强制执行,并由运行时擦除。这意味着,如果您从PHP或Hack部分模式代码中获得未类型化的值,则运行时不可能检查泛型的实际类型。请注意,未类型化的值是"信任程序员"的,因此您也可以在静态类型检查器中对它们做任何事情,请考虑以下代码,其中包括您提出的假设语法:

<?hh // partial
function get_foo() /* unannotated */ {
  return 'not an int';
}
<?hh // strict
function f(): void {
  $v = Vector<int> {};
  $v[] = 1; // OK
  // $v[] = 'whoops'; // Error since explicitly annotated as Vector<int>
  // No error from static typechecker since get_foo is unannotated
  // No error from runtime since generics are erased
  $v[] = get_foo();
}

当然,你不可能在100%严格模式代码中使用没有注释的值,但是我们必须考虑它如何与所有潜在的用法交互,包括部分模式甚至PHP中的无类型代码。

相关内容

  • 没有找到相关文章

最新更新