Janoszen

Hacklang: a PHP jövője?

Aki dolgozott már velem az tudja, hogy szeretem a PHP-t. Rengeteg olyan előnye van, amit más nyelvekben egyáltalán nem lehet megtalálni. Emellett viszont van jónéhány hülyesége is, amitől minden jóérzésű programozó tappancsokat növeszt és falra mászik. Éppen ezért jó ideje figyelem a Hacklanget, a Facebook PHP „forkját”.

Az egész Hacklang történet onnan indult, hogy a Facebookos fejlesztők gondoltak egyet és újra implementálták a PHP-t. Na nem azért, mert olyan sok fölösleges szabadidejük volt, hanem azért mert a PHP akkoriban egyszerűen túl lassú volt az igényeiknek. Ez a futtatómotor a HHVM, vagyis a HipHop Virtual Machine. Régen akár négyszeres előnnyel bírt a szabvány PHP motorral szemben, de ez a PHP 7 megjelenésével csak néhány százalékra csökkent.

Ahogy azonban a HHVM fejlődött, jött vele egy másik technológia is: a Hacklang. Ez egy olyan programnyelv, ami szerkezetileg azonos a PHP-val, ám rengeteg PHP-s baromságot javítottak benne. Olyannyira kompatibilis az ősével, hogy egy eszköz segítségével oda-vissza lehet konvertálni a kódot a két nyelv között.

A hacklanget már csak azért is érdemes egy kicsit közelebbről megnézni, mert a PHP core fejlesztők egyértelműen inspirációt vesznek az új funkciók terén a Hackből. Nézzük hát.

Az alapok

Mint írtam, a Hack szerkezetileg azonos a PHP-val, tehát ugyanúgy lesznek benne .php fájlok és rendelkezésre állnak ugyanazok a függvények, osztályok. A leglényegesebb különbség az, hogy a Hack fájlok nem a <?php karaktersorozattal, hanem <?hh-val kezdődnek. Ez jelzi a HHVM fordítónak, hogy itt Hacklang funkciókra is számítani kell, de egyébként akár klasszikus PHP kód is jöhet. Sőt mi több, Hacklang fájlokból közvetlenül hívhatunk PHP-s kódot is, így a megszoktott függvény-könyvtárak rendelkezésre állnak.

Arra érdemes figyelni, hogy a Hacklang a fejlesztők szándéka szerint egy szigorúan típusos nyelv, azaz akkor jár a legtöbb előnnyel a használata, ha minden tagváltozónak és visszatérési értéknek megadjuk a típusát. Ha ezt megtesszük, a típusellenörző már fordítási időben kidobja az ezzel kapcsolatos hibákat. Ez egy olyan funkció, ami a PHP-ból fájóan hiányik.

A Hacklang fejléc után jelezhetjük a fordítónak azt, hogy milyen módban kívánjuk futtatni a kódot:

`<?hh`
Partial mód. Ha megadunk típusokat, a típusellenörző ezeket ellenőrzi, de a típus nélküli változókra nem panaszkodik.
`<?hh // strict`
Szigorú mód. Ebben minden változónak meg kell adnunk a típusát, az összehasonlítás szigorúan típusosan működik (hasonlóan a PHP `declare(strict_types=1)` módjához) és nem hívhatunk közvetlenül PHP kódot. Ezen felül csak osztályokban és függvényekben írhatunk kódot.
`<?hh // decl`
Ebben a módban nem fut a típusellenőrzé, de a fordító fájlban található típus-információkat felhasználja más fájlok ellenőrzéséhez. Ez akkor hasznos, ha meglevő PHP kódot konvertálunk.

Ezzel már készen is állunk a Hack programunk írására. Na de mik is azok a funkciók, amik rendelkezésünkre állnak? A teljesség igénye nélkül nézzünk meg néhányat:

Szigorú típusosság

Hacklangben minden nem helyi változónak megadhatjuk a típusát, a függvényeknek pedig a visszatérési értékét:

1
2
3
4
5
6
7
8
9
10
11
class FormalEmailValidator {
    private <ins>bool</ins> $allowEmpty;

    public function __construct(<ins>bool</ins> $allowEmpty) {
        $this->allowEmpty = $allowEmpty;
    }

    public function getAllowEmpty() <ins>: bool</ins> {
        return $this->allowEmpty;
    }
}

Visszatérési értékeket természetesen a PHP 7-ben is tudunk megadni, de PHP-ban a hibás visszatérési értékből adódó hibákat csak futási időben kapjuk meg, amíg Hacklangben a HHVM rögtön fordításkor fog hisztizni, ezzel megkímélve minket némi anyázástól.

Konstruktor inicializálás

Ha már a fenti kódot nézzük, kit nem idegesít a privát tagváltozók incializálgatása a konstruktorban? Rövidítsük le a fenti kódot egy másik szép Hack feature-el:

1
2
3
4
5
6
7
8
9
10
11
class FormalEmailValidator {
    <del>private $allowEmpty;</del> // erre mar nincs szukseg

    public function __construct(<ins>private</ins> bool $allowEmpty) {
        <del>$this->allowEmpty = $allowEmpty;</del> // erre mar nincs szukseg
    }

    public function getAllowEmpty() : bool {
        return $this->allowEmpty;
    }
}

Igen, ez pontosan azt csinálja amit gondolsz. Létrehoz a konstruktorból egy privát tagváltozót, amit rögtön inicializál is a konstruktor paraméter alapján.

Normális string kezelés

Az egyik nagy bánatom a PHP-val az, hogy a stringek kezelése nem egységes. Tegyük fel, hogy van egy ilyen függvényem:

1
2
3
function printInvalidInputError($input) {
    echo('Invalid input: ' . (string)$input);
}

Igen, tudom, hihetetlenül bugyuta példa. De koncentráljuk a lényegre: a kapott változót átalakítjuk stringre.

Ez PHP-ban akkor fog működni, ha a változó a bool, int, float, string típusok egyike, vagy pedig egy olyan osztály példánya, amely rendelkezik egy __toString() metódussal. Ha ez egy olyan osztály, amely nem rendelkezik a __toString() metódussal, gyönyörű szép futási idejű hibát kapunk. Azaz gyakorlatilag ilyen kódot kell írni:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function printInvalidInputError($input) {
    if (
        is_int($input) ||
        is_float($input) ||
        is_string($input) ||
        is_bool($input) ||
        (
            is_object($input) &&
            method_exists($input, '__toString')
        )
    ) {
        echo('Invalid input: ' . (string)$input);
    }
}

És még megy a csodálkozás, hogy egyesek miért utálják a PHP-t. Na de Hacklangben erre (nagyrészt) van megoldás:

1
2
3
function printInvalidInputError(\Stringish $input) {
    echo('Invalid input: ' . (string)$input);
}

Igen, pontosan ennyi. Megmondjuk, hogy mi egy string-szerű izét várunk és a fordító majd kisakkozza hogy azt kapunk-e.

Normális tömb kezelés

A másik nagy szomorűságom a PHP-val a tömbök kezelése. Az, hogy a PHP keveri a hashmap és a tömb fogalmát még elnézhető lenne, de amit az objektum-orientált tömbök kezelésével csináltak…

Szóval PHP-ban van az array típus, ami egy natív tömb, kb így:

1
2
3
4
5
6
7
$foo = [
  'foo',
  'bar',
  1,
  2,
  3
];

Igen, pont annyira bonyolult mint amennyire kinéz: semennyire. Na ezen tömb kezelésére a PHP rendelkezésre bocsájt egy hadseregnyi függvényt, például sort, array_walk vagy array_udiff.

Ezen felül a PHP-ban léteznek bizonyos interface-ek, például az ArrayAccess, amivel felruházhatjuk az osztályunkat tömbszerű viselkedéssel.

Vagyis ez egy működő példa:

1
2
$blogPosts = new BlogPostList();
$blogPosts[] = new BlogPost();

Ez azért hasznos, mert BlogPostList osztályunkat a tömb elérésen kívül felruházhatjuk mindenféle függvénnyel, például hogy adja vissza azokat a blogpostokat amik egy kategóriába tartoznak, stb.

Na most, szerinted az ilyen tömb interface-ekkel megszentelt osztályokat át lehet adni tömbfüggvényeknek PHP-ban? Hát persze hogy nem. Miért is lenne kompatibilis.

Mondanom sem kell, hogy Hacklangben ezt megoldották:

1
2
3
4
5
6
7
<?hh

function test(\Indexish $a) : void {
  var_dump($a);
}

test([]);

Normális tömb kezelés 2

Tegyük fel, hogy van a PHP kódunkban egy ilyen kis részlet:

1
2
3
4
5
6
/**
 * @param BlogPost[] $blogPosts
 */
function renderRSS(array $blogPosts) {
    //...
}

Persze, a kommentben megadtuk hogy mi ott egy BlogPostokkal feltöltött tömböt várunk, úgyhogy a PHPStorm erre fog kódkiegészítést adni, na de ezt senki nem garantálja nekünk. Simán lehet, hogy egy szeméttel teli tömböt kapunk aztán majd jól kimazsolázhatjuk a hibaüzenetekből, hogy mi történt.

Ezen a ponton nem meglepő, hogy van megoldás a Hacklangben:

1
2
3
function renderRSS(array<BlogPost> $blogPosts) {
    //...
}

Sőt mi több, a kulcsok típusát is meg lehet adni:

1
2
3
function renderRSS(array<string, BlogPost> $blogPosts) {
    //...
}

Genericek

Hányszor, de hányszor előfordult már velem, hogy különböző származtatott osztályokat gyártottam a különböző adattípusok kedvéért, csak hogy legyen kódkiegészítés:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class AbstractBlogItemList {
    abstract public function get($index);
}
class BlogPostList  {
    /**
     * @return BlogPost
     */
    public function get($index) {
    }
}
class VideoBlogPostList  {
    /**
     * @return VideoBlogPost
     */
    public function get(int $index) {
    }
}

Na ennek vége:

1
2
3
4
5
6
7
abstract class BlogItemList<T as BlogItem> {
    /**
     * @return T
     */
    public function get(int $index) : T {
    }
}

Aki már programozott C++-ban vagy Javaban az ismeri ezt a fajta megadást: ezek a genericek vagy templatek. A T egy adattípust helyettesít, és amikor példányosítjuk a BlogItemList osztályt, akkor derül ki, hogy a T mi is valójában. Söt, megadhatunk megkötéseket a T-re, például itt bekorlátoztuk a BlogItem gyerekosztályaira.

Hátrányok

Nem ragozom tovább, szerintem ezen a ponton átjött a lelkesedésem a nyelv iránt. A többi fícsört megtalálot a hivatalos doksiban.

Mielőtt azonban eldobnál kapát-kaszát és holnaptól csak Hackben programoznál, beszéljünk kicsit a hátrányokról.

Hiányzó IDE támogatás

Sajnos PHP-ra egyetlen normális, kódkiegészítést adó IDE létezik, ez pedig a PHPStorm, a többi versenyző évtizedekkel kullog utána. Hacklanghez jelenleg nincs jó, kódkiegészítést adó IDE. A Facebook által fejlesztett Nuclide kódkiegészítése a SublimeText szintjén van, nem ad olyan mély, automatikus kódkiegészítést vagy automatikus refaktorálási eszközöket mint mondjuk a PHPStorm, vagy Javara szinte bármelyik modern IDE.

Valahol érthető is, hogy sem Jetbrainsék, sem más nem vágott még bele a Hack támogatás megírásába, hiszen a Facebookon kívül nem sok projekt használja még. Jetbrainséknél van erre egy nyitott funkció-igény, aki szeretne Hacklangben fejleszteni, annak mindenképpen érdemes szavaznia.

Hiányzó dokumentáció

Mint megannyi fiatal projekt esetén, a Hacklang dokumentációja kissé hiányos. A Stringish létezésére csak egy StackOverflow kérdés után jöttem rá, a hivatalos dokumentációban nem szerepel. (Igaz, a válasz perceken belül érkezett.)

Én személy szerint szabadidőm függvényében segíteni fogok a dokumentáció simogatásában, de tisztában kell lenni azzal, hogy a nyelv megtanulása némi időt és legfőképpen kisérletezést vehet igénybe.

Hiányzó extensionök

A HHVM kizárólag a leggyakrabban használt PHP kiegészítőket valósítja meg. A lista méretes, de arra ne számítsunk, hogy tetszőleges PECL kiegészítőt fel tudunk tenni. Ha a projektünk például SSH2-t használ, akkor itt workaroundok használatára lesz szükségünk.

Ronda transpile-olt kód

Mint az elején írtam, van lehetőség a Hack kód automatikus átalakítására PHP-ra. Ez a kód azonban nem lesz szép, és ami fontosabb: karbantartható. Ez a funkció kizárólag arra jó, hogy egy HHVM-et futtatni nem hajlandó ügyfél kezébe nyomjuk a projektet és sok sikert kívánjunk hozzá.

Nem 100%-os a typechecker

A HHVM typecheckere nem feltétlenül ad szigorú hibát mindenre, ami problémás lehet. A tömb iterátor függvények, például a next(), nem ellenőrizhetőek a typecheckerrel. Erre viszont nem kapunk hibát, hanem külön ellenőriznünk kell a typechecker coveraget.

A PHP jövője

Sokan szeretik a PHP-t leírni komolytalan nyelvnek. Ez talán annak köszönhető, hogy annak is indult: az eredeti rövidítés az volt, hogy Personal Home Page Tools. Az elmúlt években a core team elég látványosan rendbe szedte a nyelvet, kapott rendes Abstract Syntax Tree-t, erősen típusos üzemmódot és a Jetbrainsnek köszönhetően kevesebbet kell agyalni azon, hogy mi is a típusa az adott változónak.

Mára már nem csak néhány ezer soros HTML-PHP katyvasz projektek léteznek, hanem komoly, havi több százezer vagy akár millió dolláros elszámolási rendszerek is íródnak PHP-ban. Miért? Mert maga a nyelv egyszerű. Nincs 18 féle tömb megvalósítás benne, egyszerű bele kezdeni és üzemeltetni de a tudás plafon hihetetlenül magas. Ezekhez a nagy projektekhez viszont elegedhetetlen a fordítási idejű ellenőrzés, a szigorú típusosság és a kódkiegészítés.

Én bízom benne hogy a PHP core fejlesztők látják ezt az igényt, és azt a hozzáadott értéket amit a Hacklang képvisel és folytatják azt a jó utat amin elindultak. Bízom benne, hogy ezek a funkciók előbb-utóbb bekerülnek a PHP-ba is.

comments powered by Disqus