Ancora su lambda e closure in PHP

Anche se a pochi giorni di distanza, ho pensato di tornare a parlare di funzioni lambda e closure in PHP approfittando di un nuovo aggiornamento della RFC (con relativa patch che implementa quanto descritto) in cui sono state finalizzate alcune idee e sono stati introdotti alcuni nuovi concetti.

Prima di tutto è stata presa una decisione per quanto riguarda la sintassi dichiarativa, infatti precedentemente erano state formulate due possibilità. La prima prevedeva la cattura delle variabili esterne alla chiusura tramite una nuova keyword creata ad hoc, lexical:

$lambda = function($arg1, $arg2) { lexical $reference, &$valueByRef; // ... };

La seconda, quella che è stata effettivamente scelta, prevede invece il riutilizzo della keyword già esistente use:

$lambda = function($arg1, $arg2) use ($reference, &$valueByRef) { // ... };

Qualcuno preferiva la prima opzione e personalmente ero dello stesso avviso, ma alla fine la scelta è stata dettata dall’intenzione di raggiungere un compromesso in maniera tale da ottenere le stesse funzionalità mantenendo la consistenza con l’attuale semantica di PHP e senza intaccare la retrocompatibilità.

La prima estensione nella RFC riguarda invece l’utilizzo della keyword static per stabilire se importare o meno $this all’interno di una chiusura definita all’interno del metodo di una classe. Non effettuare l’importazione di $this all’interno di una chiusura quando non necessario permette di ottenere un risparmio non solo in termini di velocità d’esecuzione, anche si tratta di differenze veramente minimali, ma anche e soprattutto di memoria, evitando che delle long-lived closure possano tenere in vita inutilmente le istanze delle classi in cui esse sono state create. La chiusura che cattura $this infatti impedisce che il refcount interno per l’oggetto che referenzia possa arrivare a 0 e ciò ne comporta l’esclusione dal processo automatico di raccolta delle risorse che viene effettuato dal garbage collector. Vediamo un esempio pratico con tanto di risultato:

class NRK { private $_whoAmI = __CLASS__; public function foo() { $method = __METHOD__; return function() use ($method) { return sprintf("created in %s (w/ \$this):\n%s", $method, print_r($this, true) ?: 'null' ); }; } public function bar() { $method = __METHOD__; return static function() use ($method) { return sprintf("created in %s (w/o \$this):\n%s", $method, print_r($this, true) ?: 'null' ); }; } } $nrk = new NRK(); $fun1 = $nrk->foo(); $fun2 = $nrk->bar(); echo $fun1(), "\n", $fun2(); /* created in NRK::foo (w/ $this): NRK Object ( [_whoAmI:NRK:private] => NRK ) created in NRK::bar (w/o $this): null */

In realtà trovo che sarebbe molto più comodo e pulito avere $this importata in automatico nel caso essa venga esplicitamente richiamata nel corpo della closure, per il compilatore non sarebbe difficile accorgersene ed agire di conseguenza, ad ogni modo non mi piace l’uso della keyword static dal momento che rende ancora più verbosa la definizione di una chiusura.

Proseguendo con le nuove funzionalità, nell’ottica di una maggiore integrazione con PHP sono state estese le classi ReflectionMethod e ReflectionFunction implementando il metodo getClosure() che permette di ottenere una funzione lambda generata dinamicamente partendo da una funzione o da un metodo di istanza/classe:

class NRK { public function instanceMeth() { return __METHOD__; } public static function staticMeth() { return __METHOD__; } public static function methWithArgs($a, $b) { return $a + $b; } } $instance = new NRK(); $class = new ReflectionClass('NRK'); $fun1 = $class->getMethod('instanceMeth')->getClosure($instance); $fun2 = $class->getMethod('staticMeth')->getClosure(); $fun3 = $class->getMethod('methWithArgs')->getClosure(); echo $fun1(), "\n", $fun2(), "\n", $fun3(5, 3); /* NRK::instanceMeth NRK::staticMeth 8 */

Per finire, un’altra novità ispirata dall’implementazione dell’oggetto Closure riguarda la generalizzazione del concetto di oggetto invocabile attraverso l’implementazione di un nuovo metodo magico, __invoke(). Un esempio vale più di mille parole:

class CallableObject { public function __invoke () { return 'Guess what?'; } } $callable = new CallableObject(); $notCallable = new stdClass(); echo '$callable is ', is_callable($callable) ? '' : 'not ', "a callable object\n"; echo '$notCallable is ', is_callable($notCallable) ? '' : 'not ', "a callable object\n"; echo $callable(); echo $notCallable(); /* $callable is a callable object $notCallable is not a callable object Guess what? Fatal error: Function name must be a string in [...] */

Tirando le somme lo stato dell’implementazione di lambda e closure in PHP sembrerebbe essere in uno stato particolarmente avanzato al punto tale che lo stesso Andi Gutmans su php.internals ha chiesto, soprattutto ai release manager, di pensare se sia effettivamente possibile includere tutto questo già in PHP 5.3. In effetti PHP 5.3 sarà già ricco di novità di per sè (namespace e collaterali, LSB, __callStatic, etc) quindi l’introduzione di un’altra nuova feature è da valutare bene, tuttavia vista la buona salute della proposta e dei lavori per implementarla sarebbe un peccato dover aspettare un altro anno o più. Intanto però c’è anche chi si chiede: ma alla fine le novità di PHP 6 saranno rappresentate solo dal supporto per Unicode?


Puoi scrivere un commento oppure inviare un trackback dal tuo sito.

Lascia un commento

Puoi utilizzare i seguenti tag XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>