Klávesové zkratky na tomto webu - rozšířené Na obsah stránky

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_ca­llback 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 RSS 2.0 komentářů » přidat

avatar

#1 paranoiq paranoiq@centrum.cz nový

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()

Posláno 16. 7. 2008 v 5.25 | Odpovědět
Na komentář reagoval [2] David Grudl
avatar

#2 David Grudl http://davidgrudl.com nový

#1 paranoiq: 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.

Posláno 16. 7. 2008 v 5.39 | Odpovědět
avatar

#3 David Grudl http://davidgrudl.com nový

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.

Posláno 16. 7. 2008 v 6.53 | Odpovědět
avatar

#4 paranoiq paranoiq@centrum.cz nový

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

Posláno 16. 7. 2008 v 7.00 | Odpovědět
avatar

#5 Martin Hassman http://www.met.cz/ nový

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.

Posláno 16. 7. 2008 v 7.50 | Odpovědět
Na komentář reagoval [12] paranoiq
avatar

#6 Aleš Roubíček http://rarous.net/ nový

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…

Posláno 16. 7. 2008 v 8.03 | Odpovědět
Na komentář reagoval [7] David Grudl [8] paranoiq
avatar

#7 David Grudl http://davidgrudl.com nový

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

Posláno 16. 7. 2008 v 8.11 | Odpovědět
Na komentář reagoval [8] paranoiq
avatar

#8 paranoiq paranoiq@centrum.cz nový

#6 Aleš Roubíček:, #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
?>
Posláno 16. 7. 2008 v 8.24 | Odpovědět
avatar

#9 karf http://www.karf.cz nový

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.

Posláno 16. 7. 2008 v 9.12 | Odpovědět
Na komentář reagoval [11] David Grudl
avatar

#10 DiGi http://www.qr.cz nový

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

Posláno 16. 7. 2008 v 9.29 | Odpovědět
avatar

#11 David Grudl http://davidgrudl.com nový

#9 karf: 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č ;)

Posláno 16. 7. 2008 v 9.35 | Odpovědět
avatar

#12 paranoiq paranoiq@centrum.cz nový

#5 Martin Hassman: 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.

Posláno 16. 7. 2008 v 11.18 | Odpovědět
Na komentář reagoval [13] Michal Tuláček
avatar

#13 Michal Tuláček http://www.tulacek.eu/ nový

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

Posláno 16. 7. 2008 ve 12.05 | Odpovědět
avatar

#14 kaja47 http://k47.cz nový

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

Posláno 16. 7. 2008 ve 14.19 | Odpovědět
avatar

#15 Jan Tichý http://www.phpguru.cz/ nový

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.

Posláno 16. 7. 2008 v 16.45 | Odpovědět
Na komentář reagoval [16] paranoiq [17] David Grudl
avatar

#16 paranoiq paranoiq@centrum.cz nový

#15 Jan 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.

Posláno 16. 7. 2008 ve 21.54 | Odpovědět
Na komentář reagoval [17] David Grudl
avatar

#17 David Grudl http://davidgrudl.com nový

#15 Jan 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:.

Posláno 16. 7. 2008 ve 23.41 | Odpovědět
avatar

#18 morousej vojtech.zicha@hotmail.com nový

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 :)

Posláno 20. 7. 2008 ve 12.53 | Odpovědět
Na komentář reagoval [20] Pavol

#19 Jakub Vrána http://php.vrana.cz nový

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

Posláno 30. 7. 2008 ve 14.09 | Odpovědět

#20 Pavol nový

#18 morousej: 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).

Posláno 23. 3. 2009 v 16.29 | Odpovědět

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

Výtah na začátek článku na první komentář

Názory čtenářů v diskusích nejsou názory provozovatele webu, a ten za jejich obsah neodpovídá.

phpFashion © 2004, 2010 David Grudlo webu

Pokud není uvedeno jinak, podléhá obsah těchto stránek licenci Creative Commons BY-NC-ND Creative Commons License BY-NC-ND

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.