Closures si functii lambda in PHP vs. JavaScript

Read this post in English

Share on:

JavaScript si PHP suporta atat functii lambda cat si closures. Dar termenii sunt putin intelesi in ambele libaje de programare si de multe ori sunt confundati.

Functii Lambda

Mai sunt numite si functii anonime. Acestea se refera la functii care pot fi apelate fara sa fie neaparat legate de un identificator. Unul din scopurile lor este de a fi pasate ca argumente. Numele de Lambda li se trage de la Alonzo Church, inventatorul lambda calculus in 1936. In lambda calculus toate functiile sunt anonime.

JavaScript

In JavaScript functiile lambda fac parte din setul de baza si reprezinta metoda preferata de creare a unei functii.

De exemplu:

1var add = function (a, b) {
2     return a + b;
3}
4alert(add(1, 2)); // 3

Functiile lambda sunt folosite aproape in orice context cand vine vorba de JavaScript, un exemplu este:

1window.onload = function (e) {
2     alert('Pagina s-a incarcat!');
3}

PHP

In PHP, functiile lambda au fost introduse de la versiune 4.0.1 folosind create_function. In versiunea 5.3+ a fost adaugata si sintaxa similara cu JavaScript, un mod mult mai lizibil si elegant de a definii o functie.

Asta inseamna ca in PHP exista doua moduri de a genera o functie lambda:

 1// PHP 4.0.1+
 2$add = create_function('$a, $b', 'return $a + $b;');
 3
 4// vs.
 5
 6// PHP 5.3+
 7$add = function ($a, $b) {
 8     return $a + $b;
 9};
10
11echo $add(1,2); // 3

Functiile lambda pot fi folosite ca parametru pentru alte functii, cum ar fi usort:

1$array = array(4, 3, 5, 1, 2);
2usort($array, function ($a, $b) {
3     if ($a == $b) {
4          return 0;
5     }
6     return ($a < $b) ? -1 : 1;
7});

Chiar mai mult, PHP 5.3+ permite apelarea unui obiect ca o functie anonima:

1class test {
2     function __invoke($a) {
3          echo $a;
4     }
5}
6$a = new test();
7$a('test'); // 'test'

Closures

Notiunea de closure este notiunea cu adevarat neinteleasa dintre cele doua. In general confuzia apare pentru ca implementarea de cloasure poate presupune si functii lambda. Un closure se refera la capacitatea unei functii/obiect de a accesa mediul in care aceasta a fost creat(a) chiar daca executia functiei parinte s-a terminat. Cu alte cuvinte functia/obiectul intors de un cloasure pastreaza mediul in care a fost definita.

In JavaScript notiunea de closure face parte din arsenalul de baza, pentru ca limbajul nu are la baza un model obiectual traditional ci unul bazat pe prototype si functii. Dar JavaScript mai are si cateva reminescente, cum ar fi faptul ca poti folosi “new” pentru a construi un obiect pe baza unei functii care joaca roul de clasa. In PHP este mai degraba o noua posibilitate de abordare a problemelor, PHP facand parte din familia de limbaje cu model obiectual traditional.

JavaScript
In JavaScript notiunea de closure este foarte folosita, iar popularitatea se datoreaza faptului ca JavaScript nu este un limbaj obiectual traditional, ci unul functional, bazat pe mostenire prototype.

In JavaScript nu exista notiunea de Public, Private si Protected, exista doar Public si Private iar obiectele pot mostenii unele de la altele, fara sa foloseasca clase.

O alta problema este mediul (scope), care in mod implicit este cel global. Prin closure aceste probleme pot fi rezolvate intr-un mod elegant:

 1var closure = function () {
 2     var sum = 0;
 3     return {
 4          add: function (nr) {
 5               sum += nr;
 6          },
 7          getSum: function () {
 8               return sum;
 9          }
10     }
11}();
12
13closure.add(1);
14closure.add(2);
15console.log(closure.getSum());

In exemplul de mai sus, sum este o proprietate privata iar aceasta teoretic, putea fi accesata si modificata doar din functia closure. Partea interesanta este ca parantezele de la finalul definitiei de functiei semnifica faptul ca aceasta functie se va si executa si deci va intoarce rezultatul care este un obiect. In acest moment functia initiala nu mai exista decat pentru a servi obiectul intors, incapsuland astfel variabila privata.

Desi functia si-a terminat executia, prin intermediu acestul closure obiectul returnat poate accesa variabilele definite in interiorul functiei, pentru ca este mediul in care a fost creeat.

Problema devine mai interesanta atunci cand o functie intoarce o alta functie:

 1var counter = function () {
 2    var counter = 0;
 3    console.log('in closure');
 4    return function () {
 5        console.log('in functia anonima');
 6        return ++counter;
 7    };
 8};
 9var counter1 = counter();
10
11console.log(counter1()); // 1
12
13var counter2 = counter();
14console.log(counter2()); // 1
15console.log(counter1()); // 2

Output-ul va fi:

1in closure
2in functia anonima
31
4in closure
5in functia anonima
61
7in functia anonima
82

Ce se intampla de fapt este ca prima functie se executa si intoarce o functie anonima care inca mai poate accesa mediul in care a fost creata. Dupa parerea mea de aici vine confuzia dintre closures si lambda functions, pentru ca o functie intoarce o alta functie.

Diferenta dintre exemple este ca in primul exemplu functia initiala de closure se executa imediat, iar in al doilea exemplu se poate vedea ca atunci cand counter se executa intoarce un rezultat, care este de fapt o definitie de functie, care la randul lui poate fi executata. Evident si acest exemplu poate fi adaptat sa se comporte precum cel precedent folosind paranteze la final.

PHP

Cum spuneam si mai sus, in PHP notiunea de closure nu este la fel de importanta ca in JavaScript.

Daca functiile lambda sunt disponibile in limbaj incepand cu versiunea 4, closure au aparut de abea dupa PHP 5.3+

Datorita mediului (scope) de executie care este de tip block in PHP se realizeaza o incapsulare mai buna dar si mai putin flexibila fata de JavaScript. Practic in PHP trebuie specificat cu ajutorul instructiunii use ce anume va putea accesa functia anonima din closure-ul care si-a terminat executia.

 1function closure () {
 2     $c = 0;
 3     return function ($a) use (&$c) {
 4          $c += $a;
 5          echo $a . ', ' . $c . PHP_EOL;
 6     };
 7}
 8
 9$closure = closure();
10
11$closure(1);
12$closure(2);

Spre deosebire de JavaScript in PHP nu se pot realiza closure care sa intoarca un obiect, sau mai bine zis obiectul nu poate fi legat de mediul in care a fost creat, decat sa zicem, daca nu se trimit parametrii la constructor prin referinta, metoda nu foarte eleganta care nu vad in ce scenariu ar avea neaparata nevoie de closure.

Ca o paralela la exemplele  de la JavaScript, in loc de parantezele “()” de la finalul functiei, in PHP pentru a executa functia imediat dupa definire se pot folosii call_user_func() sau call_user_func_array():

 1$closure = call_user_func(function () {
 2    $c = 0;
 3    return function ($a) use (&$c) {
 4        $c += $a;
 5        echo $a . ', ' . $c . PHP_EOL;
 6
 7    };
 8});
 9
10$closure(1);
11$closure(2);