[Перевод] Сравнение систем типов PHP7 и Hack
Одной из интересных вещей в PHP7, кроме невероятной производительности, является введение скалярного type-hinting’а в сочетании с опциональным «strict» режимом. При чтении RFC я заметил, что PHP код в примерах выглядит очень похожим на Hack. Что если выполнить один и тот же код и в PHP7 и в Hack? Какая разница между ними? Вот что я узнал.Установка Получите следующий результат: $ php --version PHP 7.0.0-dev (cli) (built: Apr 23 2015 01:12:36) (DEBUG) Copyright © 1997–2015 The PHP Group Zend Engine v3.0.0-dev, Copyright © 1998–2015 Zend Technologies with Zend OPcache v7.0.6-dev, Copyright © 1999–2015, by Zend Technologies $ hhvm --version HipHop VM 3.8.0-dev (rel) Compiler: heads/master-0-gd71bec94dedc8ca2e722f5619f565a06ef587efc Repo schema: fa9b8305f616ca35f368f3c24ed30d00563544d1 Для того, чтобы не изменяя открывающих тегов в исходных файлах выполнять PHP-код в HHVM, исполняйте hhvm с флагом -vEval.EnableHipHopSyntax=true.Некоторые примеры Рассмотрим простой код:
function myLog (string $message): string { return $message; }
function add (int $a, int $b): int { myLog ($a + $b); return $a + $b; }
$result = add (1, 3); echo $result; Его выполнение в PHP7 вернет: Fatal error: Argument 1 passed to myLog () must be of the type string, integer given, called in /home/vagrant/basic/main.php on line 9 and defined in /home/vagrant/basic/main.php on line 4 Выглядит хорошо! PHP7 правильно говорит, что мы передаем целое число ($a + $b) в функцию, которая ожидает строку, и выдает соответствующее сообщение об ошибке. Посмотрим, что скажет HHVM: Catchable fatal error: Argument 1 passed to myLog () must be an instance of string, int given in /home/vagrant/basic/main.php on line 6 Появилась пара различий: HHVM называет это «catchable» фатальной ошибкой. Интересно, ведь в RFC сказано, что ошибка фактически должна совпадать с HHVM. HHVM сообщает, что ошибка в строке 6, а PHP, что проблема произошла в строке 9. В подобных случаях я бы предпочел PHP подход, нам показывается и где функция была некорректно вызвана, и где определена.
function myLog (string $message=null): string { if ($message === null) { return ''; } else { return $message; } }
echo myLog («Hello world!\n»);
echo myLog (); PHP с радостью исполняет код. Hack же возвращает ошибку: /home/vagrant/nullable/main.php:4:16,21: Please add a?, this argument can be null (Typing[4065]) Hack не позволяет нам иметь дефолтный аргумет со значением null, т.к. не смешивает понятия «необязательный аргумент» с «обязательный аргументом, который позволяет иметь дефолтное значение» (Подробнее об этом читайте в книге Hack and HHVM). Язык предлагает вам сделать аргумент nullable:
function myLog (? string $message=null): string { if ($message === null) { return ''; } else { return $message; } }
echo myLog («Hello world!\n»);
echo myLog (); Давайте попробуем что-нибудь посложнее. Что произойдет, если мы смешаем типизации в PHP? Обратите внимание, что определение strict-режима в верхней части файла не имеет никакого эффекта в HHVM.
function add (int $a, int $b): int { myLog ($a + $b); return $a + $b; }
function myLog (string $message): string { return $message; }
require 'add.php'; require 'logger.php';
$result = add (1, 3); echo $result; $ php main.php 4 $ hhvm -vEval.EnableHipHopSyntax=true main.php Catchable fatal error: Argument 1 passed to myLog () must be an instance of string, int given in /home/vagrant/separate_files_mixed/lo Для logger.php включился strict-режим, но PHP позволяет передать int в него из nonstrict-файла. HHVM в подобном случае выбрасывает исключение. Что произойдет, если мы переведем add.php в режим строгой типизации: Fatal error: Argument 1 passed to myLog () must be of the type string, integer given, called in /home/vagrant/separate_files_mixed/add.php on line 5 and defined in /home/vagrant/separate_files_mixed/logger.php on line 4 Так-то лучше. Strict-режим действует только в тех файлах, где он указан, даже если декларирующий функцию файл подразумевает иное. А что произойдет, если мы вызовем non-strict функцию, из strict-функции? Для реализации я поставил следующие значения для файлов: logger.php — non-strict add.php — strict Fatal error: Argument 1 passed to myLog () must be of the type string, integer given, called in /home/vagrant/separate_files_mixed/add.php on line 5 and defined in /home/vagrant/separate_files_mixed/logger.php on line 3 Получается, что функция является строго типизированной, если она вызывается из функции, которая объявлена в файле с соответствующим заголовком. Впрочем, это влияет только на прямые вызовы. Если мы объявим main.php строго типизированным, PHP радостно вернет нам 4, несмотря на несоответствие типов, которые мы передаем в log ().В Hack соотношение обратное. Если HHVM выполяет main.php в нестрогом режиме, и логгер написан на Hack (c hh тегом в верхней части файла), мы все равно получим ошибку типа несмотря на то, что вызываемый файл не написан на Hack.
Catchable fatal error: Argument 1 passed to myLog () must be an instance of string, int given in /home/vagrant/separate_files_mixed/logger.php on line 5 Другим интересным отличием между системами типов Hack и PHP является аннотация float. Возьмем пример:
function add (float $a, float $b): float { return $a + $b; }
echo add (1, 2); При выполнении в PHP вернется 3, хотя мы передаем int в том месте, где аннотировали float и несмотря на то, что режим строгой типизации включен. Причина заключается в том, что в PHP7 поддерживается расширенное преобразование примитивов (Widening primative conversion) при включенном строгом режиме. Это означает, что параметры аннотированные как float могут иметь значение int в тех случаях, когда возможно безопасное преобразование (почти всегда). HHVM не поддерживает подобное поведение и выбрасывает ошибку типов при исполнении приведенного выше кода: Catchable fatal error: Argument 1 passed to add () must be an instance of float, int given in /home/vagrant/main.php on line 6 Если есть еще какие-то другие различия, которые я упустил, пожалуйста, дайте знать в комментариях ниже. Я бы очень хотел глубже изучить эту тему.Заключение В то время, как Hack поддерживает множество фишек, PHP7 не поддерживает типы nullable, mixed, void возвращаемые значения, коллекции, async и т. д. Но все же меня сильно радует безопасность и читаемость, которая достигается новым strict-режимом в PHP7.После написания пары небольших проектов на Hack я понял, что не сами типы делают Hack приятным, а та самая обратная связь, которую создает язык между разработчиком и машиной. Наличие интеграции проверки типов для Hack в моем редакторе означает, что вся кодовая база анализируется за доли секунды после того, как я сохраню файл. Сразу же отсекаются глупые или неочевидные ошибки, которые я допустил. Я все чаще стал ловить себя на том, что не задумываюсь о возвращаемых значениях функций, просто пишу код по наитию, предполагая очевидные варианты. Если будет ошибка, редактор немедленно сообщит мне. Пара небольших правок и можно двигаться дальше.
PHP также всегда способствовал тесной обратной связи между машиной и разработчиком. Сохраните файл, обновите страницу в браузере, повторите до достижения результата. Быстро и удобно. Но проверка типов в Hack делает это даже быстрее. Я с нетерпением жду появления аналогичного функционала в IDE/редакторах для строгой типизации в новом PHP.