Na navigaci | Klávesové zkratky

Translate to English… Ins Deutsche übersetzen…

PHP, lambda funkce a closures

Zdá se, že PHP 5.3 bude ještě větší bomba, než jsme si mysleli. Do dlouho očekávané a neustále oddalované verze se na poslední chvíli dostaly lambda funkce a tzv. closures. Český ekvivalent termínu neznám, ale dalo by se použít anonymní nebo lambda funkce. Pokud máte zkušenost s JavaScriptem nebo jazykem Ruby, budou vám připadat velmi povědomé:

$lambda = function () {
    echo 'Hello World!';
};

$lambda(); // vypíše Hello World!

Lambda funkce najdou uplatnění především tam, kde potřebujeme vytvořit callback na jedno použití. Příkladem jsou třeba volání preg_replace_callback nebo array_map, které bylo doposud spojeno s nutností deklarovat funkci, která však pro zbytek programu byla zcela zbytečná. Od PHP 5.3 ji bude možné nahradit lambda funkcí:

// convert to camelCaps
$s = 'hello-world';
$s = preg_replace_callback(
    '#-([a-z])#',
    function ($matches) { return strtoupper($matches[1]); },
    $s
);
echo $s; // vypíše helloWorld

Closures rozšiřují schopnosti lambda funkcí tak, že jim zpřístupní (vybrané) proměnné z kontextu, ve kterém jsou deklarované:

function getMultiplier($product)
{
    return function ($value) use ($product) {
        return $value * $product;
    };
}

// $multi23 je "násobička 23"
$multi23 = getMultiplier(23);
echo $multi23(3); // vypíše 69

Jiným příkladem je přesměrování výstupu do souboru:

function redirect($file)
{
    $handle = fopen($file, 'w');
    ob_start(function($buffer) use ($handle) {
        fwrite($handle, $buffer);
    });
}

redirect('output.html');
echo 'Hello World!'; // místo na obrazovku se vypíše do souboru

Jak to funguje interně? Lambda funkce a closures jsou representovány objektem finální třídy Closure s jedinou magickou funkcí __invoke(). Ta sama o sobě zajišťuje, že s objekty spolupracují interní funkce a lze je použít všude tam, kde se očekává pseudotyp callback. Také is_callable($lambda) vrací TRUE. Zapouzdření callbacku tak dostává nový rozměr:

class Salute
{
    public function __invoke($name)
    {
        echo "Hello $name!";
    }
}

$salute = new Salute;
$salute('World'); // vypíše Hello World!

Jsou lambda funkce v PHP skutečně tak převratnou novinkou? V PHP je totiž lze do jisté míry nahradit skrze eval nebo create_function. Jenže za jakou cenu – tělo funkce je nutné zapsat jako řetězec, což je nepřehledné a nefunguje zvýrazňování kódu v IDE, takové funkce nelze debugovat, je znemožněno použití opcode cache atd. Dá se říci, že lambda funkce legalizují osm let starou funkci create_function pro použití ve zdravém kódu.

Podrobný popis najdete v propozicích a můžete si je rovnou i vyzkoušet – stačí stáhnout vývojovou verzi PHP 5.3.

Komentáře

  1. paranoiq #1

    avatar

    Davide, díky za upozornění. pokud „Status: Accepted“ skutečně dodrží, tak je to super a už se těším :)

    bohužel popis jsi dokonale pomotal a to už počínaje nadpisem :P
    lambda (anonymní) funkce nejsou to samé co closures (lexikální uzávěry), i kdyz se tyhle dvě věcičky mají moc rády a často se vodí za ručičku. Lamda funkce nebudou representovány objektem finální třídy Closure a __call() nemá s třídou Closure nic společnéno.

    proto dovol, abych to uvedl na pravou míru:

    lambda funkce může existovat i bez uzávěr. tak je to například v Pythonu. jazyky kompilované do nativního kódu nemají uzávěry rády.

    lambda funkce je to co popisuješ, kdežto uzávěra je v podstatě uchování kontextu, ve kterém byla funkce vytvořena, po celou dobu její existence. například když v metodě objektu vytvořím lambda funkci a předám jí jako návratovou hodnotu někam ven, její kontext včetně $this zůstane i nadále uvnitř této funkce dostupný, a to i po zrušení všech referencí na objekt ve kterém funkce vznikla.

    v interní třídě Closures se budou skladovat uzávěry.

    navíc jak píšou v tom RFC, budou se uzávěry od obvyklého chování v jiných jazycích trochu odlišovat – defaultně bude uzávěra realizována hodnotou, nikoliv referencí a uzavírané proměnně (kromě $this) je nutné explicitně deklarovat.

    magická metoda __call() bude sloužit k tomu, aby mohly být vytvářeny objekty, které lze přímo volat – tak jako funkci v proměnné (nevím jak jinak to nazvat) nebo nově lambda funkci. syntaxe volání tedy bude pro všechny stejná: $něco()

    před 8 lety | reagoval [2] David Grudl
  2. David Grudl http://davidgrudl.com #2

    avatar

    #1 paranoiqu, díky za doplnění a vysvětlení closures, nadpis opravím, ale musím opravit i tebe ;) Ta magická metoda se jmenuje __invoke() a interní třída Closure ji skutečně používá, aby bylo možné objekt (reprezentující i lambda funkci) volat způsobem, jako se volá metoda.

    před 8 lety
  3. David Grudl http://davidgrudl.com #3

    avatar

    Doplnil jsem do článku ještě příklad na closures: funkce getMultiplier(), co vrací „násobičku“ a přesměrování výstupu do souboru.

    před 8 lety
  4. paranoiq #4

    avatar

    jééé. díky. samozřejmě, že __invoke(). nevím, kde jsem vyčaroval to __call()? takhle brzo ráno bych měl radši spát :P

    před 8 lety
  5. Martin Hassman http://www.met.cz/ #5

    avatar

    Ad Český termín: Closures v jedné knize odvážně přeložili jako „uzávěry“, ale bez vysvětlení, tak jsem měl problém, než mi došlo, co tím. Lepší překlad ale asi nikdo nedá. Uvidíme, zda se ujme.

    A teď vidím, že i první komentující používá stejný termín.

    před 8 lety | reagoval [12] paranoiq
  6. Aleš Roubíček http://rarous.net/ #6

    avatar

    A teď v tom mám guláš :) V C# jsou anonymní funkce a lambda výrazy. V PHP lambda funkce jsou jako anonymní funkce v C#, v Pythonu lambda funkce spíš jako lambda výrazy v C#. Au takhle po ránu…

    před 8 lety | reagoval [7] David Grudl [8] paranoiq
  7. David Grudl http://davidgrudl.com #7

    avatar

    #6 Aleši Roubíčku, ale co je to proti rozdílu mezi overloadingem v PHP a jinde…

    před 8 lety | reagoval [8] paranoiq
  8. paranoiq #8

    avatar

    #6 Aleši Roubíčku, #7 David Grudl ony i ty uzávěry jsou jinačí než jinde.
    v PHP na rozdíl od jiných jazyků budou proměnné v uzávěře předávány defaultně hodnotou (což není tak úplně uzávěra, to umí přeci i ta create_function). předání referencí je nutné explicitně uvést pomocí „&“. pak teprv by se to chová jako plnohodnotná uzávěra.

    uvedu jeden velmi ošklivý příklad s násobičkou závislou na globální proměnné:

    <?php
    $m = 1;
    $multi = function ($value) use (&$m) {return $value * $m;};
    $a = $multi(5); // 5 jdu na sever...
    $m = -1;
    $b = $multi(5); // -5 a už jdu na jih
    ?>
    před 8 lety
  9. karf http://www.karf.cz #9

    avatar

    Tak s tím bude asi ještě hodně srandy.

    Předpokládám, že objekty se budou předávat normálně jako reference, ale není to tam explicitně zmíněno.

    Zaujala mě jedna věc – uvnitř metod bude $this importováno do anonymních funkcích defaultně, to znamená, že jakákoliv anonymní funkce vrácená z metody třídy s sebou potáhne v closure referenci na onen objekt. Naštěstí tomu lze zabránit deklarací static.

    před 8 lety | reagoval [11] David Grudl
  10. DiGi http://www.qr.cz #10

    avatar

    Lamba funkce jsou fajn, ale pomalu zajímavější mi přijdou jmenné prostory: http://blog.felho.hu/…espaces.html

    před 8 lety
  11. David Grudl http://davidgrudl.com #11

    avatar

    #9 karfe, ony objekty se normálně nepředávají jako reference, ale chápu co tím myslíš, PHP 4 a jeho smrtící klonování už je naštěstí dávno pryč ;)

    před 8 lety
  12. paranoiq #12

    avatar

    #5 Martine Hassmane, s překladem „uzávěry“ už jsem se setkal už na několika místech. jen si nejsem jistý, jestli je to ta uzávěra nebo ten uzávěr.

    před 8 lety | reagoval [13] Michal Tuláček
  13. Michal Tuláček http://www.tulacek.eu/ #13

    avatar

    #12 paranoiqu, To je matematický pojem z univerzální algebry. Česky uzávěr množiny, anglicky set closure. Od toho to imho bude odvozeno.

    před 8 lety
  14. kaja47 http://k47.cz #14

    avatar

    jak na to tak koukám, tak péhápéčko bude doopravdy bomba

    před 8 lety
  15. Jan Tichý http://www.phpguru.cz/ #15

    avatar

    Přišlo by mi elegantní a objektově čisté, pokud by PHP mělo nějaké rozhraní, nazvěme ho třeba Callable, které by svým implementacím nakazovalo implementovat jednu metodu, řekněme třeba __invoke(). Třída Closure i Tvoje ukázková Salute by pak byly implementacemi tohoto rozhraní.

    Například při kontrole předávaného callbacku v deklaraci formálních parametrů by pak šel použít klasický type-hinting namísto méně elegantního volání is_callable() uvnitř těla funkce apod.

    před 8 lety | reagoval [16] paranoiq [17] David Grudl
  16. paranoiq #16

    avatar

    #15 Jane Tichý, bylo by to krásné, ale na většině toho co je nyní v PHP volatelné nelze interface implementovat, protože nejde o objekty – řetězcová proměnná se jménem funkce, pole s objektem a jménem metody, pole s jménem třídy a jménem statické metody. tyhle dodnes jedinné možnosti předávání callbaku přece nejde ze dne na den zavrhnout.

    před 8 lety | reagoval [17] David Grudl
  17. David Grudl http://davidgrudl.com #17

    avatar

    #15 Jane Tichý, jo, přesně to jsem si říkal, když jsem četl propozice. Navíc se zdálo, že vývojáři PHP už tento směr přijali, protože nejnovější „magické“ vlastnosti se přes rozhraní implementují (Countable, ArrayAccess, Serializable).

    Na druhou stranu je fakt, callback a objekt implementující __invoke je krapet něco jiného, plus to co píše #16 paranoiq.

    před 8 lety
  18. morousej #18

    avatar

    Na lambda funkcích PHPka mi vadí délka zápisu, porovnejte:
    C#:

    var i = 3;
    list.ForEach(c => c * i);

    PHP:

    $i = 3;
    array_map($list, function($c) use($i) { return $c * $i; });

    Ale zase lepší než nic :)

    před 8 lety | reagoval [20] Pavol
  19. Jakub Vrána http://php.vrana.cz #19

    Pro zajímavost: O anonymních funkcí v PHP se začalo mluvit v březnu 2007.

    před 8 lety
  20. Pavol #20

    #18 morouseji, Zabúdaš, že v C# je list objekt a v PHP je array_map iba funkcia. Nič ti nebráni vytvoriť si podobnú triedu ako list aj v PHP (len ju musíš nazvať inak, lebo list je jazykový konštruktor).

    před 8 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.