Порівняння систем типів 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
.

Деякі приклади

Розглянемо простий код:

<?php
declare(strict_types=1);

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 підхід, нам показується і де функція була некоректно викликана, і де визначена.
<?hh
declare(strict_types=1);

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
:

<?hh
declare(strict_types=1);

function myLog(?string $message=null): string {
if ($message === null) {
return ";
} else {
return $message;
}
}

echo myLog("Hello world!\n");

echo myLog();

Давайте спробуємо що-небудь складніше. Що станеться, якщо ми змішаємо типізації в PHP? Зверніть увагу, що визначення strict-режиму верхньої частини файлу не має ніякого ефекту в HHVM.

<?php

function add(int $a, int $b): int {
myLog($a + $b);
return $a + $b;
}

<?php
declare(strict_types=1);

function myLog(string $message): string {
return $message;
}

<?php

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
x
міткою у верхній частині файлу), ми все одно отримаємо помилку типу незважаючи на те, що викликається файл не написаний на 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. Візьмемо приклад:

<?php
declare(strict_types=1);

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.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.