Categorie
PHP

PHP – Closure e funzioni anonime

A partire dalla versione 5.3, anche PHP dispone della possibilità di creare funzioni senza dichiararne il nome.
Le funzioni anonime (conosciute anche con il nome di lambda functions) e le closures (chiusure) permettono di fare proprio questo.

A mio avviso la forza principale delle funzioni anonime è quella di poterle utilizzare senza dover creare per forza delle funzioni separate, che magari non servono in nessun’altra parte dell’applicazione.

Uno degli utilizzi più comuni è la possibilità di essere utilizzate come valori di callback, tuttavia possono trovare applicazione anche per molte altre finalità.
[box type=”note”]I callback non sono altro che delle funzioni utilizzate come parametri di altre funzioni.[/box]

Un po’ di storia

I concetti di funzioni anonime e closure non sono assolutamente nuovi, anzi per dire la verità, PHP arriva decisamente in ritardo. Meglio tardi che mai!

Tali concetti derivano dalla programmazione funzionale (JavaScript è uno dei linguaggi più popolari che li supporta). Per programmazione funzionale si intende un paradigma di programmazione che sposta il focus dall’esecuzione di comandi alla valutazione delle espressioni. Queste espressioni sono formate utilizzando le funzioni, che vengono combinate al fine di ottenere i risultati cercati.

Le chiusure sono state originariamente sviluppate nel 1960 come parte di Scheme, uno dei più noti linguaggi di programmazione funzionale.
Funzioni lambda e chiusure si vedono spesso in linguaggi che permettono alle funzioni di essere trattate come valori/oggetti di prima classe, il che significa che le funzioni possono essere create al volo e passate come parametri in altre funzioni.
[box type=”note”]Un modo alternativo per definire la programmazione funzionale potrebbe essere programmazione orientata alle espressioni.[/box]

create_function

Prima di PHP 5.3 era tuttavia possibile creare delle funzioni anonime in PHP attraverso la funzione create_function.
Si tratta di una “forma embrionale” di funzione anonima (non closure) in cui ad ogni sua chiamata viene generata una funzione con nome univoco il quale viene restituito sotto forma di stringa.
create_function accetta due parametri, il primo sono gli argomenti della funzione, il secondo il codice che ne determinerà il valore di ritorno.
Ecco un semplice esempio:

$quadrato = create_function('$x', 'return $x*$x;');
echo "Nuova funzione anonima: $quadrato\r\n";
echo $quadrato(3) . "\r\n";
// outputs
// Nuova funzione anonima: lambda_1
// 9

[box type=”note”]Da notare che i parametri dovranno essere passati all’interno di apici singoli. In caso volessimo utilizzare quelli doppi dobbiamo ricordarci di eseguire l’escaping dei nomi delle variabili es: \$x.[/box]

Funzioni anonime e Closures con PHP 5.3

Come detto dalla versione 5.3 anche PHP supporta a tutti gli effetti sia le funzioni anonime che le chiusure.
Tuttavia nella documentazione ufficiale questi due termini sembrano avere lo stesso significato ed essere usati in modo intercambiabile.
Essi a mio avviso non sono gli stessi, anche se molte persone li presentano allo stesso modo.

Funzioni anonime

La funzioni lambda (o “funzioni anonime”) sono semplicemente funzioni usa e getta, che possono essere definite in qualsiasi momento e che sono in genere associato ad una variabile.

Vediamo ora in che modo possiamo, a partire da PHP 5.3, creare la stessa funzione anonima dell’esempio precedente utilizzando questa volta invece una sintassi molto più pulita, leggibile ed assimilabile ad altri linguaggi funzionali:

$quadrato = function($x) {
    return $x * $x;
};
echo $quadrato(3);
// output
// 9

Non vi è ombra di dubbio che questo è un modo molto più bello (visivamente) di definire una funziona anonima. La sintassi è proprio quella di una funzione, manca solo il nome, piuttosto del “pasticcio” precedente in cui sia il corpo che i parametri erano passati come argomenti di un’altra funzione.
Un altro problema generato dell’utilizzo di creata_function(), è che la funzione sarà generata a runtime non traendo vantaggi da un eventuale caching dell’opcode visto che la funzione non potrà essere memorizzata in cache.
Leggere eventualmente questo articolo per approfondire sul caching del codice intermedio.
[box type=”note”]Per dire la verità in rete si trovano diversi test che dimostrano come, l’utilizzo di creata_function() sia più performante rispetto alle funzioni lambda. C’è da considerare che questi test, tuttavia, non tengono conto dell’utilizzo di sistemi di caching dell’opcode.[/box]

Closure

In definitiva le funzioni anonime non aggiungono niente rispetto a quello che era possibile fare con  create_function(). Si tratta sempre di funzioni usa e getta, la differenza sta soltanto nella sintassi estremamente più leggibile e pulita e nel fatto che vengono eseguite a compile-time e no a run-time.

Con le closure invece, otteniamo un salto di qualità. Esse ci permettono di allargare l’ambito della funzione, dandoci la possibilità di vedere quello che invece non poteva essere visto.

Quando una funzione è dichiarata, ha la capacità di fare riferimento a tutte le variabili che sono dichiarate nel suo ambito. Una variabile dichiarata al di fuori della funzione non sarà quindi “visibile” al suo interno.
Questo non dovrebbe essere una sorpresa per qualsiasi categoria di sviluppatore.

Le chiusure, in parole povere, non sono altro che funzioni anonime che conoscono alcune variabili che non sono state definite al loro interno. (Per chi proviene da JavaScript questo concetto dovrebbe essere piuttosto familiare).

La possibilità, soprattutto per le funzioni di callback, di fare riferimento alle variabili locali in vigore quando sono state dichiarate è uno strumento essenziale per la scrittura di codice efficace.

Vediamo ora un esempio di closure:

$saluto = 'Ciao Mondo';
$closure = function() use($saluto) {
    return $saluto;
};

echo $closure(); // Ciao Mondo

La clausola use in pratica permette alla funzione di importare nel proprio scope la variabile $saluto e di farne uso.
La variabile di default viene importata per valore, ciò significa che se aggiorniamo il valore della variabile importata nella closure, la variabile esterna non sarà aggiornata.
Vediamo ora un esempio di passaggio per riferimento, creando il classico contatore:

$counter = 1;
$closure = function() use(&$counter) {
    echo $counter++ . "\r\n";
};

$closure();
$closure();
$closure();
/*
outputs
1
2
3 
 */

Nell’esempio notiamo come ad ogni chiamata la closure aumenti (e stampi a video) il valore della variabile esterna $counter.
Vediamo ora un’esempio di come possiamo utilizzare una closure come valore di ritorno di una funzione, in questo caso una funzione anonima, tanto per rimanere in tema:

$agginugi = function($valore_da_aggiungere) {
    return function ($x) use ($valore_da_aggiungere) {
        return $x + $valore_da_aggiungere;
    };
};

$agginugi_6 = $agginugi(6);
$agginugi_8 = $agginugi(8);

echo $agginugi_6(10); // 16
echo $agginugi_8(10); // 18

Closure negli oggetti

Nella programmazione OOP possiamo servirci delle closure esattamente come avviene in quella procedurale.
Come esempio simuliamo un carrello della spesa:

class Cart
{
    private $_items = array();
    private $_total = 0;


    public function __construct($items) {
        $this->_items = $items;
    }
    
    public function getTotal($tax)
    {
        $callback = function ($item) use ($tax) {
            $this->_total += ($item['qta'] * $item['price']) * ($tax + 1.0);
        };
        
        array_walk($this->_items, $callback);
        
        return $this->_total;
    }        
}

$items = array(
    array(
        'name'  => 'latte',
        'qta'   => 3,
        'price' => 1.5
    ),
    array(
        'name'  => 'pane',
        'qta'   => 2,
        'price' => 3.00
    ),    
);

$cart = new Cart($items);
echo $cart->getTotal(0.21); //12.705

Nell’esempio abbiamo utilizzato la closure nel metodo getTotal() come funzione di callback.
Due sono le cose da notare:

  1. Attraverso la closore abbiamo potuto utilizzare la variabile $tax altrimenti non accessibile.
  2. Quando definite all’interno di un oggetto, le closure hanno pieno accesso all’oggetto tramite la variabile $this, senza la necessità di importarlo in modo esplicito (nell’esempio abbiamo incrementato la proprietà $_total).

[box type=”note”]La possibilità di utilizzare $this nelle funzioni anonime è stata introdotta con la versione 5.4 di PHP.[/box]

Il metodo __invoke ()

A partire da PHP 5.3 è stato introdotto il nuovo metodo magico __invoke() che permette ad una classe di essere chiamata come una funzione.

The __invoke() method is called when a script tries to call an object as a function.

class CallableClass
{
    public function __invoke($x)
    {
        var_dump($x);
    }
}
$obj = new CallableClass;
$obj(5); //int(5)
var_dump(is_callable($obj)); //bool(true)

A mio avviso richiamare questo metodo può essere utilizzato per passare una classe affinchè agisca come una closure, o semplicemente come una funzione che è possibile passare in giro.

Conclusioni

In questo articolo abbiamo visto cosa sono le funzioni anonime (o lambda) e le closure.
Anche se la documentazione le tratta come se fossero la stessa cosa mi è piaciuto, invece, spiegartene la differenza. Le closure non sono altro che funzioni anonime “potenziate” dal punto di vista dello scope.
Abbiamo visto infine come le closure possono essere usate non solo all’interno della programmazione procedurale ma anche di quella orientata agli oggetti.
Sicuramente chi ha avuto a che fare con la programmazione funzionale o per chi proviene da JavaScript non dovrebbe aver nessun problema a comprendere questi concetti, altrimenti spero che questo articolo abbia contribuito a schiarire le idee.

Concludo condividendo un video del grande Anthony Ferrara pubblicato proprio mentre scrivevo l’articolo. Direi che casca proprio a fagiolo 😉

4 risposte su “PHP – Closure e funzioni anonime”

La funzione agginugi() mi ricorda un mio carissimo amico: all’università, anno 2002, andava ovviamente di moda far sviluppare a tutti gli studenti programmi legati alla palindromìa. Lui preparò un programmetto per verificare le frasi palindrome che chiamò per un refuso PALINDORMA. Il nome era così simpatico che lo mantenne ed entrò nel nostro set di ricordi nerdosi preferiti ^__^.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.