Почему в Hack запрещен универсальный синтаксис создания экземпляров?
Из документов:
Примечание: 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
, Но нужно проследить ошибку до того момента, когда некорректные данные были фактически импортированы. В этом не было бы необходимости, если бы можно было создать экземпляр вектора с использованием общего синтаксиса:
$myVector = new Vector<int>([]);
$myVector->addAll(require 'some_external_source.php'); // fail immediately
1 ответ
Я работаю над системой взлома и проверкой типов на Facebook. Этот вопрос несколько раз задавался внутренне в FB, и было бы хорошо иметь хорошее внешне видимое место, чтобы записать ответ на него.
Итак, прежде всего, ваш вопрос основан на следующем коде:
<?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> {}
"на самом деле", но иногда я действительно хочу аннотировать местных жителей ". Таким образом, правильная языковая функция не позволяет вам писать $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.