Por ítalo Lelis de Vietro / @italolelis
Hack is a language for HHVM that interoperates seamlessly with PHP. The goal of Hack is to offer developers a way to write cleaner, safer and refactorable code while trying to maintain a level of compatibility with current PHP codebases.
O type checker é o sua melhor linha de defesa. Contudo, em tempo de execução é permitido coisas que o type checker não aprova.
O type checker é invocado utilizando o hh_client que vem instalado com o Hack. Esse executável atualmente está disponível apenas para plataformas *nix.
Type annotations allow for PHP code to be explicitly typed on parameters, class member variables and return values (types are inferred for locals). These annotated types are checked via a type checker.
<?hh //strict
function sum(int x, int y): int {
return x + y;
}
<?hh //strict
class TestResult
{
protected int $runCount;
protected array<Failure> $failures;
}
<?hh //strict
function invoke((function(int $x): int) $fn): int {
return $fn(1);
}
<?hh //strict
class FluentObject
{
public function doThing(): this
{
//do something
return $this;
}
}
<?hh //strict
namespace HackPack\HackUnit\Runner;
class Options
{
protected string $testPath;
/**
* Use Options type since "this" annotation is broken
* when namespaces are used
*/
public function setTestPath(string $testPath): Options
{
$this->testPath;
return $this;
}
}
O type checker do Hack tem algumas maneiras interessantes de lidar com o tipo "mixed", que para muitos programadores PHP é familiar. O compilador irá identificar quando você tentar validar o tipo.
<?hh
function sum(mixed $x): void {
if (is_array($x) || $x instanceof Vector) {
$s = 0;
foreach ($x as $v) {
$s += $v;
}
return $s;
}
//... do something else or throw an exception...
}
<?hh //strict
function infiniteIterator(): Continuation<int> {
$i = 0;
while (true) {
yield ++$i;
}
}
<?hh
async function f(): Awaitable<int> {
return 42;
}
async function g(): Awaitable<string> {
$f = await f();
$f++;
return 'hi test ' . $f;
}
O compilador do Hack tem vários níveis de tolerância. Eles são conhecidos como modes, e são disparados por comentários.
O nível mais purista. Tudo deve estar tipado e tudo é verificado pelo type checker. Código no modo strict não pode chamar um código que não é escrito em Hack.
<?hh //strict
class TestCase
{
public function __construct(protected string $name)
{
}
public function setUp(): void
{
}
public function expect<T>(T $context): Expectation<T>
{
return new Expectation($context);
}
}
O modo strict é tão rigoroso que você não poderá ter um ponto de partida no sistema, ou seja, você não pode ter um "main", uma vez que ele estará fora dos padrões strict. Para isso usamos o Partial ou UNSAFE.
Os arquivos hhi contém interfaces para quase todas as funções do core do PHP. Esses arquivos basicamente disponibilizam informações sobre os tipos de cada função para o compilador do Hack. Você só precisa garantir que eles estejam na raiz do seu projeto.
Partial mode is the default of Hack. In partial mode, the type checker checks all types other than that encompassed by an // UNSAFE comment. Partial mode also allows for the partially typing of a class, method or function (e.g., only type a subset of its arguments). And, also unlike strict mode, partial mode allows engineers to call code that has not yet been "Hack-ified" (in other words, they can call into untyped code).http://docs.hhvm.com/manual/en/hack.modes.partial.php
Ja que o modo strict não permite código de nível superior, o modo parcial é o método para entrada do programa, ou seja o nosso metodo "main".
https://github.com/HackPack/HackUnit/blob/master/bin/Hackunit
Decl mode is used to allow Hack code written in strict mode to call into legacy code, without having to fix the issues that would be pointed out by partial mode. The type checker will "absorb" the signatures of the code, but will not type check the code. Decl is mainly used when annotating old, existing APIs (i.e., when the code does note meet Hack's stricter subset of PHP).
// UNSAFE disables the type checker from the point of unsafe declaration until the end of the current block of code (where the end of the current block generally refers to the associated ending brace (}) of which the // UNSAFE is declared).https://github.com/HackPack/HackUnit/blob/master/Runner/Loading/StandardLoader.php#L98
Hack introduces generics to PHP (in the same vein as statically type languages such as C# and Java). Generics allow classes and methods to be parameterized (i.e., a type associated when a class is instantiated or a method is called).
<?hh //strict
namespace HackPack\Hacktions;
trait EventEmitter
{
protected Map<string, Vector<(function(...): void)>> $listeners = Map {};
}
A preferência por inferência parece ser um golpe para a legibilidade. Os seguintes resultados são obtidos por um erro de tipo:
$fun = () ==> { $fn = $this->callable; $fn(); }
$this->expectCallable($fun)->toThrow<ExpectationException>();
//Tests/Core/CallableExpectationTest.php|54 col 70 error| This operator is not associative, add parentheses
$fun = () ==> { $fn = $this->callable; $fn();};
$this->expectCallable($fun)->toThrow('\HackPack\HackUnit\Core\ExpectationException');
A generic method must not collide with any existing, non-generic method name (i.e, public function swap and public function swap).
<?hh //strict
class Cook
{
use Subject<Waiter>;
use Subject<Busboy>;
}
//throws type errors
Hack introduces a safer way to deal with nulls through a concept known as the "Nullable" type. Nullable allows any type to have null assigned and checked on it.
<?hh //strict
class Options
{
protected ?string $HackUnitFile;
public function getHackUnitFile(): ?string
{
$path = (string) getcwd() . '/Hackunit.php';
if (! is_null($this->HackUnitFile)) {
$path = $this->HackUnitFile;
}
$path = realpath($path);
return $path ?: null;
}
}
<?hh //strict
class TestResult
{
protected ?float $startTime;
public function getTime(): ?float
{
$time = null;
$startTime = $this->startTime;
$time = microtime(true) - $startTime;
return $time;
}
//TestResult.php|39 col 35 error| Typing error
//TestResult.php|39 col 35 error| This is a num (int/float) because this is used in an arithmetic operation
//TestResult.php|13 col 15 error| It is incompatible with a nullable type
}
<?hh //strict
class TestResult
{
public function getTime(): ?float
{
$time = null;
$startTime = $this->startTime;
if (!is_null($startTime)) {
$time = microtime(true) - $startTime;
}
return $time;
}
}
A Vector is an integer-indexed (zero-based) collection with similar semantics to a C++ vector or a C#/Java ArrayList. Random access to elements happen in O(1) time. Inserts occur at O(1) when added to the end, but could hit O(n) with inserts elsewhere. Removal has similar time semantics.
A Map is an ordered dictionary-style collection. Elements are stored as key/value pairs. Maps retain element insertion order, meaning that iterating over a Map will visit the elements in the same order that they were inserted. Insert, remove and search operations are performed in O(lg n) time or better
A Set is an ordered collection that stores unique values. Unlike vectors and maps, sets do not have keys, and thus cannot be iterated on keys.
Nota: Sets suportam apenas chavez inteiras ou string por enquanto.
A Pair is an indexed container restricted to containing exactly two elements. Pair has integer keys; key 0 refers to the first element and key 1 refers to the second element (all other integer keys are out of bounds).
To address the shortcomings of PHP closures, HHVM introduced a "lambda expression" feature. Lambda expressions offer similar functionality to PHP closures, but they capture variables from the enclosing function body implicitly and are less verbose in general.
$ui = new Text();
$this->runner->on('testFailed', (...) ==> $ui->printFeedback("\033[41;37mF\033[0m"));
$this->runner->on('testPassed', (...) ==> $ui->printFeedback('.'));
$squared = array_map($x ==> $x*$x, array(1,2,3));
$ui = new Text();
$this->runner->on('testFailed', (...) ==> $ui->printFeedback("\033[41;37mF\033[0m"));
//vs
$this->runner->>on('testFailed', function(...) use ($ui) {
$ui->printFeedback("\033[41;37mF\033[0m";)
});