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
paranoiq #1
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()
David Grudl #2
#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.
David Grudl #3
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.
paranoiq #4
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
Martin Hassman #5
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.
Aleš Roubíček #6
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…
David Grudl #7
#6 Aleši Roubíčku, ale co je to proti rozdílu mezi overloadingem v PHP a jinde…
paranoiq #8
#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é:
karf #9
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.
DiGi #10
Lamba funkce jsou fajn, ale pomalu zajímavější mi přijdou jmenné prostory: https://web.archive.org/…espaces.html
David Grudl #11
#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č ;)
paranoiq #12
#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.
Michal Tuláček #13
#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.
kaja47 #14
jak na to tak koukám, tak péhápéčko bude doopravdy bomba
Jan Tichý #15
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řídaClosure
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.paranoiq #16
#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.
David Grudl #17
#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.
morousej #18
Na lambda funkcích PHPka mi vadí délka zápisu, porovnejte:
C#:
PHP:
Ale zase lepší než nic :)
Jakub Vrána #19
Pro zajímavost: O anonymních funkcí v PHP se začalo mluvit v březnu 2007.
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).
Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.