Negli ultimi giorni sulla mailing list di sviluppo di PHP si sta discutendo sulla possibilità di estendere il linguaggio in maniera tale da introdurre i concetti di funzioni lambda e di closure tanto cari a molti linguaggi dinamici come Ruby, Python, JavaScript, etc. In realtà si potrebbe quasi considerare come un ritorno di fiamma, già in passato ci furono discussioni simili anche se poco convinte e poco convincenti, tuttavia questa volta si intravede la possibilità di sfociare in una decisione furba nonostante non ci sia particolare interesse nell’implementazione da parte di alcuni dei core-dev (tanto per cambiare). Attualmente il tutto ruota intorno a una RFC che, oltre a mettere in evidenza alcuni scenari sull’utilizzo di funzioni lambda e chiusure e riportare i dettagli implementativi nell’engine di Zend, allega alcune patch con alcune piccole varianti sul tema da applicare ai sorgenti nei rami di sviluppo di PHP 5.3 e PHP 6.0 per poter toccare con mano quanto si sta discutendo.
$fun = function($name = 'guy') { echo "Hi $name!"; };
printf("Tipo: %s\n", get_class($fun)); // Type: Closure
printf("Callable: %s\n", is_callable($fun)); // Callable: 1
Come possiamo notare, è stato implementato un nuovo tipo in ZE denominato Closure. Closure risulta essere invocabile, alla stessa stregua di funzioni e metodi:
echo $fun(); // Hi guy!
echo $fun('NRK'); // Hi NRK!
Il primo pensiero va a questo punto a tutte quelle funzioni in PHP che accettano delle callback ma in cui comodità ed espressività vengono pesantemente minate dall’attuale modus operandi del linguaggio che prevede due opzioni:
- definire una funzione tradizionale da usare come argomento callback ma sotto forma di stringa (il nome della funzione stessa), dando quindi origine a potenziali bugs galore.
- creare una funzione a runtime usando create_function, opzione che oltre a risultare poco elegante alla vista rischia di introdurre un’altra buona dose di problemi in termini di manutenibilità del codice.
Ecco quindi un confronto tra come è possibile scrivere codice oggi e la possibilità di sviluppare la stessa soluzione attraverso l’utilizzo delle funzioni lambda:
$array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
/* senza funzioni lambda */
function reducer($a, $b) { return $a * $b; }
$res = array_reduce($array, 'reducer', 1); // 3628800
/* senza funzioni lambda */
$res = array_reduce($array, create_function('$a, $b', 'return $a * $b;'), 1); // 3628800
/* con funzion lambda - versione "lunga" */
$reducer = function($a, $b) { return $a * $b; };
$res = array_reduce($array, $reducer, 1); // 3628800
/* con funzion lambda - versione "breve" */
$res = array_reduce($array, function($a, $b) { return $a * $b; }, 1); // 3628800
Il fatto che l’oggetto Closure sia a tutti gli effetti visibile in userland permette anche di sfruttare l’hinting dei tipi nel caso volessimo limitare a questo tipo di oggetto alcuni parametri delle nostre funzioni:
function iterate($array, Closure $fun) {
foreach ($array as $element)
$fun($element);
}
iterate($array, 'function'); // genera un Catchable Fatal Error, Argument 2 passed to
// iterate() must be an instance of Closure, string given
Essendo Closure una vera e propria classe (che, per la cronaca, attualmente espone solo un metodo __toString() seppur vuoto) potrebbe implementare in un ipotetico futuro diverse funzionalità interessanti e chissà, potrebbe anche essere serializzabile. Ad ogni modo, dopo le funzioni lambda passiamo a vedere le closure:
$array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
$sum = 0;
$fun = function($element) {
lexical &$sum;
// ...
$sum += $element;
};
array_walk($array, $fun);
echo $sum; // 55
Possiamo notare l’introduzione della keyword lexical (c’è anche la variante use a seconda della patch che si decide di utilizzare) che ci permette di catturare i riferimenti alle variabili esterne al corpo della chiusura. L’introduzione di lexical è dovuta al fatto che con questa implementazione non viene catturato tutto l’ambiente lessicale in cui viene creata la chiusura, come accade invece in altri linguaggi, per motivi legati principalmente alle regole di scope di PHP. Usando lexical possiamo pertanto decidere quali variabili catturare, ma è da notare che attualmente per i tipi numerici o stringa, che per default in PHP sono passati per valore, è necessario creare manualmente un riferimento anteponendo l’ampersand alla variabile catturata, come da esempio. Tecnicamente la cattura per riferimento dovrebbe essere di default anche per questi tipi e lo stesso creatore della patch è del medesimo avviso, tanto più che il terribile global si comporta nello stesso modo creando sempre riferimenti.
In linea di massima l’introduzione di funzioni lambda e closure in PHP sarebbe interessante e in certe situazioni renderebbe la scrittura dei programmi un po’ meno arrabattata con la possibilità di ottenere codice molto più facilmente generalizzabile e più verificabile/manutenibile. Provare è abbastanza facile: basta avere un ambiente configurato per compilare PHP, scaricare i sorgenti di PHP 5.3 o 6.0 dal repository CVS (sì, usano ancora CVS…), applicare una delle patch disponibili, compilare e installare.