-
Nota: Titanium si PhoneGap sunt doua platforme pentru dezvoltarea de aplicatii hibride pentru mobil. Aceste aplicatii nu sunt scrise in limbajul de programare nativ telefonului, dar pot fi distribuite pe mai multe platforme mobile.
Recent, am publicat pe Github cea mai noua si extraordinara platforma de teste, Just-Quizzing pe numele ei. Am facut-o ca sa o folosesc eu personal, dar pentru ca am gasit cativa colegi care au considerat-o utila, am facut-o publica.
In versiunea initiala foloseam SL4A pentru a publica aplicatia pe telefonul meu mobil cu Android. Aveam nevoie de un mod in care sa folosesc aplicatia cand sunt in miscare si nu am acces la Intenet. Din pacate nu exista actualizare automata pentru teste. Am sperat ca SL4A va avea o directie mai clara in timp, dar platforma si-a pierdut din elanul initial, iar eu am decis sa scriu o aplicatie mobila.
Am avut de ales intre PhoneGap si Titanium. In trecut am folosit PhoneGap pentru ca marimea aplicatiei era prea mare in Titanium, dar am zis sa ii mai dau o sansa. In 2011, cand am mai scris despre Titanium, marimea unei aplicatii de baza era aproximativ 4M. In ziua de azi nu mi se mai pare chiar atat de mare, desi este maricica.
Perspectiva va fi in special pentru Android, desi aplicatia a fost facuta sa fie compatibila si cu iPhone.
Am folosit SDK 3.1.3, fara framework-ul Alloy.
Alloy are si el curba lui de invatare si am vrut sa o evit. Scopul aplicatiei era sa fie o versiune portabila a testelor de pe server si nu am vrut sa fac o investitie foarte mare de timp.
Timpul total de dezvoltare a fost putin mai mult de o saptamana.
Prima parere
Lucrurile nu merg chiar asa cum te astepti, nu ai foarte multa flexibilitate. Din timpul total alocat, mai mult de un sfert am incercat sa fac un “flyout menu”, asa cum au aplicatiile ca: Facebook, Google Plus si Youtube. A fost un dezastru total! Singurele variante care chiar pareau sa mearga au fost folosirea unui modul extern sau folosirea widgetul-ui din Alloy.
Se pare ca la conversia dintre coordonatele relative, unde utilizatorul a atins elementul curent, la punctul efectiv, unde esti pe ecran, exista o discrepanta mare si este imposibil sa faci un efect decent de deschidere a meniului. Intr-un final am renuntat total la acest efect.
Viteza nu este chiar atat de buna cand vine vorba de randare de elemente. Sincer, ma asteptam la o viteza mai buna. Exista mici trucuri care se pot face, de exemplu sa preincarci view-urile inainte sa le afisezi, dar pentru asta trebuie sa interpretezi cam ce va face utilizatorul in continuare, lucru care nu este posibil mereu.
Un alt elemente care m-a deranjat este ca la apasarea butonului de meniu pe Android nu poti pune o actiune anume, sa zicem sa deschida meniul lateral de care vorbeam anterior. In PhoneGap de exemplu, nu se va deschide nici un meniu, acesta trebuie construit din HTML de la zero.
Cand au aparut, Titanium se laudau ca poti dezolta o data si distribui pe mai multe platforme. Acum am avut surpriza ca nu mai au aceasta abordare. In Kitchen Sink, aplicatia care demonstreaza facilitatile platformei, fiecare tip de platforma initializeaza aplicatia in fisierul ei. Unul dintre motive este ca design-ul se face dinamic daca nu vrei sa folosesti Alloy, nu prin fisiere de layout.
Printre primele lucruri pe care le-am incercat cu Titanium a fost sa vad cat este marimea unei aplicatii simple de “Hello World”. Impachetata, aplicatia ocupa ~8.5M, instalata pe telefon ~16M. Nu stiu ce parere aveti voi, dar mie mi se pare foarte mult! Ciudat este ca aplicatia mea finala ocupa ~8.4M impachetata si ~16M instalata, aproape identic cu aplicatia originala. Motivul este simplu, imaginile puse de mine ocupa cu cativa kb mai putin, rezultand in final o aplicatie putin mai mica decat aceea de la care am pornit.
Dar de ce ocupa o aplicatie Titanium atat de mult?
Am avut de cautat pana am gasit raspunsul. In 2011, cand am incercat sa ma documentez, nu am gasit decat ca sunt impachetate toate librariile chiar daca sunt necesare sau nu si din acest motiv este atat de mare. Nu asta era cauza! Intre timp, am aflat adevaratul motiv. Aplicatia ruleaza folosind un “bridge” intre Android si JavaScript. Codul JavaScript nu este “compilat”, asa cum reiese din prezentarile initiale, ci pur si simplu interpretat.
Codul JavaScript este interpretat folosind unul din motoarele disponibile in aplicatie (V8 si Rhino) si apeleaza componentele care construiesc elementele grafice native.
Problema este la interpretoare (engine). Acestea sunt destul de mari si sunt practic prezente in fiecare aplicatie. Si, de parca nu erau destul de mari, sunt si 3, fiecare cu scopul lui:
- V8 – pentru versiuni de Android noi (dupa Gingerbread 2.3);
- Rhino – pentru versiuni vechi de Android (inainte de Gingerbread 2.3);
- x86 – pentru procesoare de tip Intel.
Aceste 3 engine-uri ocupa in total, dezarhivate, 19M pe calculatorul meu. Deci, fara sa eliminam o parte din ele nu avem cum sa scadem dimensiunea.
O metoda prin care cei de la Appcelerator vor sa scada dimensiunea apk-ului este sa renunte la Rhino, dar in acest mod se va pierde si compatibilitatea cu versiuni mai vechi de Android.
Comunitate
Exista o comunitate foarte activa. Pe forumuri si platforme Q/A este plin de intrebari si raspunsuri! Trebuie sa recunosc ca am fost impresionat din acest punct de vedere. Unele intrebari sunt relativ vechi, chiar si de 3 ani de zile.
Curba de invatare
Pentru o saptamana si aproape jumatate, din care mai bine de un sfert am cautat cum sa fac un efect de miscare al unui view, care pana la final nu a functionat, eu zic ca a mers destul de repede!
Recunosc, m-am mai jucat acum 3-4 ani cu el pentru cateva zile si chiar mi-am amintit anumite lucruri, cum ar fi ca erau elemente de tip window, view si label.
Faptul ca gasesti usor raspunsuri la intebarile de pe Stackoverflow, adunate in cativa ani, dar si ca exista o documentatie destul de bine organizata a ajutat foarte mult.
De asemenea si pe Youtube exista cateva tutoriale utile.
Mediul de dezoltare
Pentru primele versiuni nu exista un IDE, aplicatiile erau scrise cu un editor la alegere, apoi impachetate si testate folosind platforma.
Acum exista un IDE care are la baza Eclipse. Numele acestuia este Titanium Studio. Acesta are un autocomplete destul de reusit, care ajuta in cele mai multe cazuri. Are si cateva lucruri care nu merg prea bine, cum ar fi faptul ca mie uneori nu mi-a mai mers syntax highlight, fara sa-mi indice unde este problema. De asemenea, administrarea proiectelor mi se pare un pic ciudata si am avut cateva probleme la importul si exportul de proiecte.
Nu exista implicit o unealta vizuala de editare a elementelor din fereastra. Atat Xcode cat si ADT permit editarea de elemente cu drag & drop. Se pare ca exista si unelte de acest tip, dar nu sunt gratuite.
Emulatoarele nu sunt foarte configurabile. Partea buna este ca nu trebuie sa-ti bati capul, selectezi sistemul de operare si tipul de display si Titanium va configura automat un emulator. Partea proasta este ca poate mi-ar placea sa am un management al resurselor mai clar.
Un alt dezavantaj este ca uneori aplicatia pur si simplu moare pana porneste emulatorul, iar daca incerci din nou va incerca sa mai porneasca un emulator fara succes, caz in care trebuie inchis emulatorul, rulat Project clean si apoi incercat din nou, cu grija sa nu ratam momentul cand se poate debloca ecranul, sau scenariul de la inceput se va repeta.
Pentru iPhone nu merg log-urile cand vine vorba de OS X Mavericks.
Parti bune
Se invata relativ usor.
Documentatia este destul de bine realizata, iar acolo unde ceva nu este clar, sigur a mai intebat altcineva aceeasi intrebare.
Exista o comunitate destul de mare.
IDE-ul este usor de folosit.
Este usor de vazut cam ce este posibil in aplicatia Kitchen Sink.
Aplicatia nu arata ca o pagina web intr-un WebView!
Parti rele
Aplicatia ocupa foarte mult spatiu (16M pe telefon).
Elementele grafice sunt doar niste subseturi din platformele suportate.
Nu este foarte rapid, din cauza faptului ca avem codul intepretat si nu compilat, ca sa poata interactiona direct cu OS-ul.
Nu exista un editor vizual.
Exista probleme de integrare cu emulatoarele, atat pentru Android cat si pentru iPhone.
Concluzie
Daca esti un cunoscator al limbajului JavaScript, ai nevoie repede de o aplicatie si vrei sa foloseasca elemente grafice native, atunci probabil Titanium este pentru tine.
Daca vrei performanta, probabil trebuie sa te orientezi spre altceva.
Eu nu am suficienta experienta cu SDK-ul Android cat sa pot construi aceasta aplicatie in timpul care l-am avut, acesta a fost compromisul meu.
Sincer, nu sunt sigur daca PhoneGap era o optiune mai buna sau nu, vorbind de cazul meu paticular. Astazi exista multe librarii de JavaScript facute special sa emuleze elementele grafice native intr-o aplicatie PhoneGap. Diferenta nu este neaparat vizibila si, pana la urma, uneori poate aplicatia chiar nu trebuie sa arate ca o aplicatie standard, poate o interfata complet diferita este mai potrivita.
-
Motivat de articolul lui Rober Martin – Hoards Of Novices, am decis sa scriu si eu un articol pe un subiect similar.
Nu vreau sa scriu despre nevoia angajatorilor de a angaja juniori, ci despre de ce acesti juniori nu se ridica la nivelul asteptarilor, iar pentru final, despre ce am putea face pentru o mai buna pregatire.
Piata software este intr-o continua expansiune. Software-ul este modul in care comandam calculatorului ce sa faca. Astazi, dispozitivele din jur au procesoare, software si aplicatii, de la telefoane, ceasuri, televizoare si pana la masini de spalat si frigidere. Toate aceste dispozitive au nevoie de programe si programatori ca sa dezolte aplicatii pentru ele.
Cu aceste premize ajungem la criza software. S-a ajuns la concluzia ca avem nevoie de software mai repede pentru a rezolva aceasta criza. Acesta a fost un subiect important in anii ’60, dar cu timplul lumea nu i-a mai dat importanta.
Pentru a rezolva aceasta problema au aparut limbaje de nivel inalt si framework-uri. Limbajele de nivel inalt fac dezoltarea si intretinerea unei aplicatii mult mai simpla. In acest mod programatorii se indeparteaza foarte mult de “metal”, de hardware-ul efectiv, lucrand cu notiuni mult mai usor de manipulat.
Intr-o lume a limbajelor de nivel inalt (Java, C#, PHP, Python, JavaScript etc.) este mult mai usor si mai rapid sa compui o aplicatie.
Si totusi, desi modul de dezoltare al aplicatilor devine mai simplu, calitatea programatorilor este si ea in scadere.
O societate de consum
Avem nevoie de scoala pentru o slujba mai buna.
Pentru ca nevoia de studii reprezinta astazi o nevoie generala, scoala in sine se dilueaza. Principiile educationale in mare parte nu au suferit schimbari fundamentale de cand educatia a devenit disponibila tuturor. Rezultatul procesului de invatare este examenul, pentru ca asa se determina nivelul cunostintelor. Si mai presus, un rezultat mediocru ne permite sa avansam, fara sa demonstreze ca baza evaluata este prezenta.
Trecerea la nivelul urmator se face print-o examinare partiala, nu trebuie sa stapanesti 100% din notiuni, ci doar 50-60%. Nu este neaparat clar daca in celalate 40-50% se afla notiuni importante.
Aceasta problema a educatiei este mai mult sau mai putin o problema generala si sunt putini cei care chiar incearca abordari diferite.
In general, eu nu consider scoala un sistem definitoriu de stabilire a cunostinelor, mai ales astazi, cand avem acces la un volum atat de mare de alternative.
Cand studentii termina scoala, ei nu au toate notiunile pentru a lucra efectiv in industrie, ceea ce nu este atat de anormal. Scoala trebuie sa ofere doar o baza generala. De exemplu, in invatamantul superior pot sa-mi amintesc cel putin 8-9 limbaje de programare invatate. Efectiv au fost mai multe, dar nu am tinut o evidenta detaliata. Este clar ca nu ai cum sa cunosti atatea limbaje de programare la nivelul la care sa produci. Pe de alta parte, ai o idee de abordare, care te poate ajuta sa stabilesti o directie in care vrei sa aprofundezi.
Aceasta este cheia, sa aprofundezi! Pentru asta fiecare are abordarea lui, aici poti sa-ti alegi singur calea, fie cursuri, carti, proiecte practice sau toate la un loc. Prin aprofundare ajungi la nivelul la care angajatorii nu doar te accepta, dar te vaneaza!
De multe ori, pasiunea inlocuieste total studiile. Am cunoscut mai multe persoane care, desi nu au studiat informatica, sunt acum profesionisti in ea.
Partea buna este ca multa lume aprofundeaza de placere si sfatuiesc pe oricine citeste aceste randuri sa caute placerea in ce fac. Aceasta este cea mai buna motivatie.
Educatie alternativa intr-o societate de consum
Pentru ca metoda traditionala de invatamant nu este tocmai suficienta, sa vorbim de cateva metodele alternative:
- carti
- cursuri
- tutoriale
- articole/bloguri
Carti
Cartile in mod traditional sunt cea mai buna sursa de informatii (cel putin pana la aparitia Internetului). Dar calitatea nu este garantata!Acum 8-9 ani am incercat sa cumpar o carte pentru JavaScript. Selectia nu era foarte mare asa ca am incercat sa analizez fiecare carte in parte.
La final am fost dezamagit de alegerea facuta pentru ca in loc sa ma invete JavaScript, cartea ma invata cum sa fac efecte speciale pe pagini (foarte populare in perioada respectiva).
In cazul meu particular, am gasit raspunsul mult mai tarziu intr-o prezentare a lui Douglas Crockford. Acesta spunea ca majoritatea cartilor de JavaScript sunt de acest fel si a recomandat o singura carte in afara de cartea scrisa de el, JavaScript: The Definitive Guide.
Cauza este simpla, lumea este mult mai atrasa de carti care promit sa te invete un limbaj sau tehnologie intr-un numar limitat de lectii sau zile. Vrem totul acum, dar studiul are nevoie de timp, trebuie sa intelegi notiunile, nu doar sa le treci in revista intr-un mod rapid.
In general, ca sa determini calitatea unei carti ar trebui sa cauti review-uri. In ziua de astazi este foarte usor, eu prefer Amazon.com dar nu numai. Asa poti determina daca este ce cauti.
Uneori si o perspectiva rapida este utila, mai ales daca nu ai alt mod de abordare pentru subiect, dar in general orice subiect abordat rapid nu-ti ofera o perspectiva generala, ci doar o introducere usoara.
Cursuri
Sunt multe cursuri, atat in clasa cat si on-line. La fel ca si in cazul cartilor, este greu sa determini calitatea cursului.
Cursurile promit multe, dar de multe ori nu trateaza in detaliu subiectele, iar la final chiar daca ai impresia ca ai invatat multe, cand vine vorba de aplicat realizezi ca orice variatie de la materialele cursului te pune in dificultate.
Ca si in cazul cartilor, referintele sunt foarte importante! Din pacate pentru cursuri nu sunt la fel de multe ca pentru carti.
Incearca sa vezi subiectele abordate si detaliul de abordare. Sau uneori poti gasi mai repede referinte pentru profesor decat pentru curs.
Tutoriale
In special pe Internet sunt foarte multe tutoriale care promit sa te invete un domeniu, dar nu reusesc sa te invete cum sa realizezi exemplul lor. La final nu poti realiza decat mici variatiuni de la exemplul initial. De multe ori, cel care face tutorialul are cele mai bune intentii, dar este greu sa faci un transfer de informatii.
Pentru cele care sunt gratuite este foarte simplu sa determini daca abordarea este potrivita sau nu pentru nevoile privitorului. Incearca sa privesti cateva episoade si vezi daca la final ai inteles cum functioneaza componenta respectiva. Daca poti doar replica, atunci poate mai ai de cautat.
Partea buna este ca sunt foarte multe si ai de unde alege in general. De exemplu, la un limbaj de programare poti vedea in referinta oficiala cam ce ar trebui sa inveti, apoi in tutorial vezi daca ai inteles punctele din referinta si ai o idee ce fac.
La variantele comerciale este mult mai greu, pentru ca de multe ori ai doar un preview introductiv, insuficient pentru a-ti forma o parere. In aceste cazuri ar trebui sa cauti review-uri.
Bloguri si articole
Partea buna aici este ca investitia in timp este relativ mica. E simplu sa-ti formezi o parere la final.
Dezavantajul este ca unele articole nu se actualizeaza o data cu tehnologia. Trebuie sa te asiguri ca exemplele functioneaza pe versiunea folosita de tine. Din experienta proprie pot spune ca acest lucru este greu si necesita mult timp. De exemplu, unele articole care inca mai atrag cititori pe acest blog sunt scrise acum cativa ani si, daca nu mai folosesc tehnologiile respective, nu mai sunt tentat sa le tin la curent.
Cum sa invatam mai bine
Cauta referinte pentru materialele care presupun o investitie de timp si/sau bani: carti, cursuri sau tutoriale.
Atunci cand te hotarasti sa abordezi un subiect, incearca sa vezi ce materiale recomanda altii. Internetul este plin de materiale de diferite grade de dificultate.
Cand te simti stapan pe un subiect, testeaza-ti cunostintele. Construieste o aplicatie, urmareste subiectul pe platforme de Q/A sau, de ce nu, scrie un tutorial pentru altii. Intelegi mult mai bine un subiect cand il descrii altora.
Exerseaza! Este foarte important sa folosesti ce inveti sau vei uita mai repede decat crezi. Contribuie la proiecte open-source, incearca sa-ti faci proiecte personale sau incearca sa adaptezi proiectele altora la nevoile tale. Github este o unealta forte buna pentru a gasi si publica proiecte.
Este putin mai greu la inceput sa intelegi codul altuia, dar in industrie este imperativ ca sa poti colabora cu altii.
Invata lucruri noi tot timpul. Tehnologia se schimba, la fel cum se schimba si perspectiva asupra ei. De exemplu, AJAX a aparut la sfarsitul anilor “90, dar perspectiva asupra cum acesta se poate folosi s-a schimbat dramatic in anii urmatori, chiar daca tehnologia nu s-a schimbat mult timp. La fel a fost si cu JavaScript, desi a apatur la inceputul anilor “90, abia dupa anii 2000 a inceput sa fie considerata o unealta utila, inainte era folosita in special pentru efecte speciale.
Mult timp, PHP a fost considerat un limbaj util doar pentru proiecte mici. Astazi, este limbajul preferat de giganti ai internetului cum ar fi Facebook si Yahoo!. O data cu popularitatea si abordarea asupra lui a fost schimbata pentru o performanta si scalabilitate mai buna.
Concluzie
Sistemul educational nu ofera performanta de care angajatorii au nevoie. Asta creeaza o discordanta intre asteptarilor angajatorilor si cele ale absolventilor, iar asta nu este doar o problema a informaticii.
Din pacate si materialele alternative uneori promit lucruri nerealiste, unii studiaza tehnologii si limbaje de programare luni de zile, iar altii promit rezultate dupa doar cateva zile sau chiar ore.
Din fericire, traim intr-o lume a informatiei si putem gasi nu doar materiale ci si pareri despre acestea.
Printr-o mai buna pregatire, sansele de angajare cresc considerabil, rezultand intr-o satisfactie mai mare de ambele parti.
-
Cea mai noua versiune de PHP este aproape gata. In momentul cand scriu acest blog, PHP 5.5 este in RC 1.
Cum am mai spus si in blogul anterior, lista de noutati este disponibila la: http://www.php.net/manual/en/migration55.new-features.php
Al doilea feature ca popularitate este “finally”: http://www.php.net/manual/en/language.exceptions.php
Care-i treaba cu “finally“?
Pare un pic confuz, pare un bloc care se executa dupa ce se termina blocul de try catch. Dar ce este nou?
Sa zicem ca avem un bloc try/catch:
PHP 5.x < 5.5:
1// open resouce 2$resource = new Resouce(); 3try { 4 // do stuff with the resouce 5 $resouce->doStuff(); 6} catch (Exception $e) { 7 // log exception 8 syslog(LOG_ERR, $e->getMessage()); 9} 10// release resouce 11unset($resouce);
PHP 5.5
1// open resouce 2$resource = new Resouce(); 3try { 4 // do stuff with the resouce 5 $resouce->doStuff(); 6} catch (Exception $e) { 7 // log exception 8 syslog(LOG_ERR, $e->getMessage()); 9} finally { 10 // release resouce 11 unset($resouce); 12}
Pana aici nu exista nici un motiv pentru care este nevoie de un nou bloc. Am prins exceptia, am facut logging pe ea si am continuat.
Dar sa zicem ca este o resursa si vrem sa o eliberam, iar ulterior sa aruncam exceptia. O varianta ar fi sa eliberam resursa in catch.
Dar mai ramane cazul “fericit”, sa zicem ca trebuie sa o eliberam si atunci.
1// open resouce 2$resource = new Resouce(); 3try { 4 // do stuff with the resouce 5 $resouce->doStuff(); 6} catch (Exception $e) { 7 // release resouce 8 unset($resource); 9 // perpetuate exception 10 throw $e; 11}
Sa complicam si mai mult, sa zicem ca avem n tipuri de exceptii. Vor rezulta n conditii de catch, plus 1 pentru cazul fericit, iar in toate trebuie sa eliberam resursa. Nu foarte eficient…
O alta varianta este sa stocam exceptia intr-o variabila si, dupa ce am eliberat resursa, sa aruncam si exceptia, daca este cazul.
1// variable to store the exception 2$exception = false; 3 4// open resouce 5$resource = new Resouce(); 6try { 7 // do stuff with the resouce 8 $resouce->doStuff(); 9} catch (Exception $e) { 10 $exception = $e; 11} 12 13// release resouce 14unset($resource); 15 16if($exception) { 17 throw $exception; 18}
Acesta este unul din modurile in care se realizeaza in prezent. Functioneaza, dar nu evidentiaza faptul ca poate doar vrem sa eliberam resursa si sa ne continuam viata in liniste.
Varianta PHP 5.5
In manualul php.net:
In PHP 5.5 and later, a finally block may also be specified after the catch blocks. Code within the finally block will always be executed after the tryand catch blocks, regardless of whether an exception has been thrown, and before normal execution resumes.
De fiecare data cand blocul se executa, indiferent daca se executa cu succes sau nu, finally se va executa. Deci, pentru exemplul:
1try { 2 echo 'Do stuff' . PHP_EOL; 3 throw new Exception('testing'); 4} finally { 5 echo 'inside finally' . PHP_EOL; 6}
Outputul va fi:
1Do stuff 2inside finally 3 4Fatal error: Uncaught exception 'Exception' with message 'testing' in...
Daca vrem sa prindem si exceptia:
1try { 2 echo 'Do stuff' . PHP_EOL; 3 throw new Exception('testing'); 4} catch (Exception $e) { 5 echo 'do something with the exception' . PHP_EOL; 6} finally { 7 echo 'inside finally' . PHP_EOL; 8}
Outputul va fi:
1Do stuff 2do something with the exception 3inside finally
Si chiar daca luam cazul si mai particular, cand prindem exceptia, apoi o aruncam:
1try { 2 echo 'Do stuff' . PHP_EOL; 3 throw new Exception('testing'); 4} catch (Exception $e) { 5 echo 'do something with the exception' . PHP_EOL; 6 throw $e; 7} finally { 8 echo 'inside finally' . PHP_EOL; 9}
Pare ca acum am reusit sa prevenim executia blocului finally? Nu este tocmai asa…
1Do stuff 2do something with the exception 3inside finally 4 5Fatal error: Uncaught exception 'Exception' with message 'testing' in...
Cu alte cuvinte, blocul finally se executa de fiecare data, indiferent de rezultat.
-
O noua versiune de PHP este pe cale sa se lanseze. In momentul cand scriu acest blog, PHP 5.5 este in beta4.
Dornic de a vedea noutatile, am compilat noua versiune beta. Lista de noutati este disponibila la: http://www.php.net/manual/en/migration55.new-features.php
Cel mai important feature il reprezinta generatoarele (generators).
Generare de generators in PHP 5.5
Un generator este practic o functie care contine un apel catre “yield”.
Sa luam exemplul de pe php.net:
1<?php 2function xrange($start, $limit, $step = 1) { 3 for ($i = $start; $i <= $limit; $i += $step) { 4 yield $i; 5 } 6} 7 8echo 'Single digit odd numbers: '; 9 10/* Note that an array is never created or returned, 11 * which saves memory. */ 12foreach (xrange(1, 9, 2) as $number) { 13 echo "$number "; 14} 15?>
Practic, generatorul (xrange in acest caz), in loc sa intoarca un array, o sa genereze cate o valoare pentru a fi prelucrata.
Dar stai… oare asta nu era deja posibil pana la aceasta versiune?
Generatori inainte de PHP 5.5
Pana la versiunea de PHP 5.5 aveam deja iteratori:
1<?php 2 3class xrange implements Iterator 4{ 5 private $position = 0; 6 private $start; 7 private $limit; 8 private $step; 9 10 public function __construct($start, $limit, $step = 1) 11 { 12 $this->start = $start; 13 $this->limit = $limit; 14 $this->step = $step; 15 $this->position = 0; 16 } 17 18 function rewind() 19 { 20 $this->position = 0; 21 } 22 23 function current() 24 { 25 return $this->start + ($this->position * $this->step); 26 } 27 28 function key() 29 { 30 return $this->position; 31 } 32 33 function next() 34 { 35 ++$this->position; 36 } 37 38 function valid() 39 { 40 return $this->current() <= $this->limit; 41 } 42} 43 44echo 'Single digit odd numbers: '; 45 46/* Note that an array is never created or returned, 47 * which saves memory. */ 48foreach (new xrange(2, 9, 2) as $number) { 49 echo "$number "; 50} 51?>
In afara de faptul ca Iteratorul este un obiect cu mai multe proprietati, practic putem atinge acelasi rezultat.
Dar de ce era nevoie de generatori atunci? Simplu! In loc sa folosim ~40 linii de cod, putem folosi pur si simplu 5 ca sa atingem acelasi scop.
Un alt lucru interesant este ca:
1get_class(printer());
va intoarce Generator.
Deci, practic, un generator va intoarce inapoi un obiect de tip Generator, iar acest obiect extinde Iterator.
Diferenta majora, asa cum este si pe site-ul php.net, este ca generatorul nu poate fi resetat, merge intr-o singura directie.
Trimiterea de informatii catre generator
Da, generatorii functioneaza in doua sensuri, doar ca un anume generator este bun doar pentru un singur sens. Daca sintaxa de mai sus este pentru “producerea” de rezultate, sintaxa de mai jos este pentru “consumare” de date.
Sintaxa pentru un generator “consumator” este simpla:
1<?php 2function printer() { 3 $counter = 0; 4 while(true) { 5 $counter++; 6 $value = yield; 7 echo $value . $counter . PHP_EOL; 8 } 9 echo ‘Never executed...' . PHP_EOL; 10} 11 12$printer = printer(); 13$printer->send('Hello!'); 14echo 'Something is happening over here...' . PHP_EOL; 15$printer->send('Hello!'); 16?>
Outputul va fi:
1Hello!1 2Something is happening over here... 3Hello!2
Practic, valoarea din yield poate fi folosita ca orice alta valoare. Ce este interesant este while-ul. Pe php.net este urmatorul comentariu:
// Sends the given value to the
// generator as the result of
// the yield expression and
// resumes execution of the
// generator.Este nevoie de un loop, pentru ca generatorul se va opri dupa ce proceseaza valoarea si va continua doar atunci cand primeste o noua valoare. Daca scoatem while-ul, doar prima valoare va fi procesata, indiferent de cate ori vom apela send().
Un lucru interesant este ca ceea ce este dupa loop nu se va executa, in cazul meu linia:
1echo ‘Never executed...' . PHP_EOL;
Desi pare un loc potrivit sa eliberezi o resursa (ex. BD sau fisier), de fapt nu este, pentru ca acel cod nu se va executa.
Mi se pare util la logging. Din nou, nimic ce nu putea fi facut si pana acum, dar totusi permite o abordare mult mai usoara.
Am descoperit totusi un lucru care nu functioneaza:
1<?php 2function printer() { 3 while(true) { 4 echo yield . PHP_EOL; 5 } 6} 7 8$printer = printer(); 9$printer->send('Hello world!'); 10 11foreach($printer as $line) { 12 echo $line . PHP_EOF; 13}
Un pic haotic, nu? Eram curios ce se intampla:
Fatal error: Uncaught exception ‘Exception’ with message ‘Cannot rewind a generator that was already run’ in…Odata ce a fost folosit send() pe un iterator, nu mai poti sa iterezi prin el. Evident se poate genera unul nou, cu:
1printer();
Ce este si mai confuz este ca Generator este o clasa final, deci nu poate fi extinsa, iar daca incerci sa o instantiezi direct (desi chiar daca ar functiona ar fi inutil):
Catchable fatal error: The “Generator” class is reserved for internal use and cannot be manually instantiated in…Concluzia
Este un feature interesant, pentru ca simplifica mult lucrurile atunci cand vrei sa construiesti un iterator.
De asemenea, functionalitatea de send() mi se pare foarte interesanta, nu pentru ca face ceva nou, ci pentru ca il face mai usor.
Nu-mi place in schimb ca este aceeasi sintaxa pentru ambele variante de generatori si mai mult, ce este dupa while nu se mai executa.
Mi se pare usor confuza sintaxa, pentru ca nu este o diferentiere clara. Pe de alta parte, se pare ca asta exista deja in Python, deci pentru inspiratie se pot folosi exemplele din acest limbaj.
-
Notiunea de closure in PHP, desi a aparut in PHP 5.3, a fost realizata intr-un mod adecvat abia in 5.4, asa cum am mai spus-o si pe blogul meu.
Wikipedia ne spune:
In computer science, a closure (also lexical closure or function closure) is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables) of that function.
In PHP nu este un concept foarte popular sau foarte cunoscut. De multe ori acesta este confundat cu Anonymous Functions. In limbajele functionale totusi, acest concept este foarte popular, pentru ca acolo este cu adevarat nevoie de el!
Scheme
Cand Brendan Eich a conceput JavaScript, s-a bazat pe limbajul Scheme si a ajuns sa faca o implementare a acestuia cu o sintaxa de C. Sintaxa C era si este in continuare mult mai populara, iar atunci (1995) limbajul Java era foarte “la moda”.
Sintaxa Scheme este similara cu sintaxa Lisp, in sensul ca se folosesc paranteze in jurul expresiilor pentru a le rula. Operatorii sunt definiti ca si functii si la fel ca o si in cazul functiilor, se pun in partea stanga a parantezei.
Sa luam un exemplu de closure in Scheme:
1(define (make-counter) 2 (let ((count (begin 3 (display "run parent function and return 0") 4 ))) 5 (lambda () 6 (set! count (+ count 1)) 7 (begin 8 (display "inside child function ") 9 count))))
Functia principala seteaza o variabila “count”, cu valoarea 0 si afisaza “run parent function and return 0”, apoi intoarce o alta functie lambda, care incrementeaza variabila definita in functia principala si apoi afisaza “inside child function”.
Functia rezultata din executia functiei principale o stochez intr-o variabila pentru a o putea rula ulterior de mai multe ori:
1> (define counter (make-counter)) 2run parent function and return 0 3> (counter) 4inside child function 1 5> (counter) 6inside child function 2
Cu alte cuvinte, de fiecare data cand apelez (make-couter), acesta va intoarce o functie noua care are acces la mediul in care a fost creata. Daca pare ciudat din pricina sintaxei, promit ca in JavaScript va parea mult mai natural.
Acest concept este foarte interesant pentru incapsulare. Mediul la tipul cand functia parinte este executata se poate incapsula, iar ulterior se va folosi de accest mediu fara grija ca acesta se poate schimba din cauze exterioare.
Pentru limbajele functionale acesta este un concept foarte interesant. Cand vine vorba de limbaje obiectuale totusi, conceptul aproape inutil, pentru ca obiectele au si ele rolul de incapsulare.
JavaScript
JavaScript a fost de la inceput un hibrid, un limbaj functional, orientat obiect, cu mostenire bazata pe prototype. Iar daca acestea nu erau suficiente, sintaxa a fost preluata din Java (C).
JavaScript nu a mostenit multe de la Scheme, dar a mostenit conceptul de closure.
Un motiv pentru care era nevoie de closure in Scheme este acela ca daca o functie nu gaseste o variabila in mediul in care se afla, o va cauta in mediul superior. Sa luam un exemplu:
1(define x 1) 2(define (add-in-env y) (+ x y))
Daca apelam add-in-env cu 2:
1(add-in-env 2) -> 3
Pare la fel de ambiguu ca si in JavaScript, dar nu este tocmai asa. In Scheme sa faci mutatie nu e la fel de usor, simplu si transparent, deci o operatie ulterioara de:
1(define x 2)
va rezulta intr-o eroare.
In JavaScript a rezultat un hibrid. Mutatia este permisa, dar notiunea de a cauta o variabila in mediul in care te afli a ramas:
1var x = 1; 2var add_in_env = function (y) { 3 return x + y; 4} 5 6add_in_env(2); // rezulta 3
Pana aici e ok, dar pentru:
1x = 2; 2add_in_env(2); // rezulta 4
In acest caz, lucrurile scapa foarte usor de sub control.
Dar, ca sa rezolvam problema, putem pur si simplu sa definim variabila in mediul care isi va termina executia (se va inchide = will close):
1var make_counter = function () { 2 console.log("run parent function and set counter to 0") 3 var count = 0; 4 5 return function () { 6 count = count + 1; 7 console.log("inside child function"); 8 return count; 9 } 10} 11 12var counter = make_counter(); 13console.log(counter()); 14console.log(counter()); 15 16var counter2 = make_counter(); 17console.log(counter2()); 18console.log(counter()); 19console.log(counter2());
Outputul va fi:
1run parent function and set counter to 0 2inside child function 31 4inside child function 52 6run parent function and set counter to 0 7inside child function 81 9inside child function 103 11inside child function 122
Chiar daca functia principala si-a terminat executia, mediul din interiorul ei este pastrat ca un closure pentru functia care a fost intoarsa. Doar in momentul in care si subfunctia nu mai are referinte catre ea memoria alocata pentru closure va fi dezalocata.
Chiar daca JavaScript are obiecte, acestea nu au metode private. O abordare este sa pui un “_” (underscore) in fata numelui functiei si sa o consideri privata. Din punctul meu de vedere asta este ca si cum ii rogi pe cei care vin dupa tine sa o considere o functie privata. Evident acest lucru nu este tocmai consistent.
Sa luam un exemplu:
1var obj = { 2 _secretFunction : function (key) { console.log(‘do secret ’ + key) }, 3 doStuff : function (key) { this._secretFunction(key) } 4} 5 6obj.doStuff(‘stuff’); // do secret stuff
Aparent avem o metoda publica “doStuff” si una privata “_secretFunction”. Totusi nu poti preveni un utilizator sa apeleze “_secretFunction”, sau mai rau, sa o modifice:
1obj._secretFunction = function (key) { console.log('new secret ' + key); } 2 3obj.doStuff('stuff'); // new secret stuff
Daca vrem ca functia sa fie ascunsa, iar acest lucru sa fie evident pentru toata lumea, din nou putem folosi un closure:
1var obj = (function () { 2 var secretFunction = function (key) { console.log(‘do secret ’ + key) } 3 4 return { 5 doStuff : function (key) { 6 secretFunction(key) 7 } 8 } 9})(); 10 11obj.doStuff(‘stuff’); // do secret stuff
Pentru ca functia parinte se va executa la inceput, practic spatiul in care a fost definit secretFunction si-a terminat deja executia, incapsuland logica. Obiectul intors poate sa apeleze functia pentru ca este definit in acelasi mediu ca si obiectul.
Pare complicat prima data, dar de fapt este foarte simplu cand intelegi conceptul.
Si apoi a fost… PHP
PHP inglobeaza multe optiuni diferite. PHP s-a dezvoltat initial ca un framework Perl, ulterior engine-ul fiind scris in C.
PHP este un limbaj dinamic care inglobeaza foarte multe concepte, de la obiecte, interfete si functii anonime, pana la goto labels. Nu este foarte clara directia in care ar trebui sa se dezolte limbajul, mai degraba ofera posibilitatea pentru abordari diferite.
In istoria ciudata a PHP, undeva in versiunea 4 a fost introdusa o sintaxa pentru Anonymous Functions, dar abia in PHP 5.3 a aparut o versiune mai “normala“.
Tot in versiunea 5.3 a fost introdusa si prima varianta de closures:
1$scalar = 5; 2 3$closure = function () use ($scalar) { 4 return 'Scalar: ' . $scalar . PHP_EOL; 5}; 6 7echo $closure(); // Scalar: 5 8 9$scalar = 7; 10 11echo $closure(); // Scalar: 5
Versiunea functioneaza in mare parte, dar trebuie sa specifici ce vei trimite catre closure.
Si mai exista cateva inconveniente:
1<?php 2class Foo { 3 private function privateMethod() { 4 return 'Inside private method'; 5 } 6 7 public function bar() { 8 $obj = $this; 9 return function () use ($obj) { 10 return $obj->privateMethod(); 11 }; 12 } 13} 14 15$obj = new Foo(); 16$closure = $obj->bar(); 17echo $closure(); 18 19Fatal error: Call to private method Foo::privateMethod() from context '' in [...][...] on line 10
Nu functioneaza pentru ca nu poti trimite $this ca parametru la closure, iar daca faci artificiul de mai sus tot nu vei putea accesa metodele private. Nu uitati, asta se intampla in PHP 5.3.
Ideea de a introduce acest tip de closure mi se pare bizara. Nu este prima daca cand in PHP se introduce un feature “bizar”, dupa cum vorbeam mai sus si de Anonymous Function. Pare work in progress.
Cred ca toata lumea se astepta ca acest feature sa functioneze la fel ca in JavaScript. Cred ca doar datorita JavaScript conceptul de closure a devenit atat de popular.
In versiunea PHP 5.4 lucrurile s-au mai schimbat, avem in sfarsit closures asa cum ne asteptam:
1class Foo { 2 private function privateMethod() { 3 return 'Inside private method'; 4 } 5 6 public function bar() { 7 return function () { 8 return $this->privateMethod(); 9 }; 10 } 11} 12 13$obj = new Foo(); 14$closure = $obj->bar(); 15echo $closure(); // Inside private method
Functioneaza!
Poti chiar sa spui:
1unset($obj); 2echo $closure();
si va functiona, pentru ca obiectul in interiorul caruia a fost definit closure-ul a ramas in memorie pana cand se va termina executia scriptului, sau se va apela:
1unset($closure);
Pentru mai multe detalii despre cum functioneaza closure in PHP 5.4, puteti citi acest blog.