Fundamentele programării (IV) - Scala

În cadrul acestui episod vom vedea cum putem face în așa fel încât programele să ia anumite decizii în funcție de circumstanțe.

Până acum am scris doar programe sau scurte secvențe de cod în care instrucțiunile se executau în ordine, începând cu prima și terminând cu ultima. Nimic nu putea afecta acest proces. Dar, programele trebuie să poată face mai mult decât atât...

Să ne imaginăm că ajungem la o răscruce de drumuri; cel din dreapta duce spre Împărăția Roșie, iar celălalt spre Împărăția Verde. În funcție de unde vrem să ajungem, vom alege drumul din dreapta sau cel din stânga.

Pentru a o lua la dreapta, trebuie ca o anumită condiție să fie îndeplinită; trebuie să vrem să mergem în Împărăția Roșie. Pentru a o lua la stânga, trebuie ca acea condiția să nu fie îndeplinită; trebuie să nu vrem să mergem în Împărăția Roșie, ceea ce înseamnă că vrem să mergem în Împărăția Verde.

Condiția poate lua diverse forme. În exemplul anterior condiția a fost voința noastră; dar, ar putea să fie ceva mai interesant; de exemplu, am putea spune că dacă în caleașca pe care o însoțim se află o prințesă, atunci vom merge în Împărăția Roșie.

Instrucțiunea if

Pentru a explica ce vrem să facem, am folosit cuvântul dacă; am spus că dacă o anumită condiție este îndeplinită vom efectua o anumită acțiune. Și în Scala vom folosi ceva similar; va fi în engleză, deci cuvântul va fi if. El va fi urmat de condiția care trebuie verificată, cuprinsă între paranteze rotunde și apoi de instrucțiunea care trebuie executată în cazul în care condiția este îndeplinită. Condiția este o expresie booleană; poate fi o simplă variabilă sau o expresie mai complexă. O astfel de instrucțiune este numită condițională.

Să presupunem că avem o variabilă existaPrintesa care are o anumită valoare; nu știm exact cum a fost atribuită acea valoare și nici care este valoarea sa curentă. Dar, dacă în acest moment această variabilă are valoarea true, dorim să scriem mesajul "Împărăția Roșie". Secvența de cod ar arăta astfel:

Secvența este corectă, dar nu arată prea bine. De obicei instrucțiunea care ar trebui executată atunci când este îndeplinită condiția este scrisă pe o linie nouă.

Blocuri de instrucțiuni

Deocamdată, dacă este îndeplinită condiția executăm o singură instrucțiune. Dar, s-ar putea să vrem să executăm mai multe. De exemplu, am putea să vrem să ducem prințesa în Împărăția Roșie și să o lăsăm acolo; când ajunge acasă, prințesa va coborî din caleașcă, deci în caleașcă nu se va mai afla nicio prințesă. Așadar, variabila existaPrintesa ar trebui să primească valoarea false. Am putea verifica din nou dacă este îndeplinită condiția; codul ar putea fi următorul:

Nu arată deloc bine... Ce am vrea este să scriem condiția o singură dată și să executăm mai multe instrucțiuni. Pentru aceasta va trebui să creăm un așa numit bloc de instrucțiuni. Un astfel de bloc este format dintr-o succesiune de instrucțiuni, cuprinse între acolade. Putem rescrie secvența astfel:

O practică des întâlnită este să se creeze blocuri de instrucțiuni formate dintr-o singură instrucțiune; codul este mai clar și sunt evitate anumite greșeli puțin mai greu de detectat.

Să presupunem că avem prima variantă: cea în care doar scriem string-ul "Împărăția Roșie". Dorim acum ca, pe lângă acest mesaj, pe o altă linie să scriem și string-ul "Prințesa a ajuns acasă". Dacă ne grăbim, codul nostru ar putea arăta așa:

Deși am aliniat instrucțiunea cu cea anterioară, spațiile se ignoră, deci nu are nicio importanță. Avem o instrucțiunei if care spună că, în cazul în care condiția este îndeplinită, se execută instrucțiunea care urmează. Cealaltă instrucțiune se execută oricum; condiția nu mai este verificată. Deci, mesajul "Prințesa a ajuns acasă" ar fi afișat chiar dacă nu am fi avut nicio prințesă în caleașcă.

Dar, dacă am fi folosit acolade chiar și în prima variantă:

atunci, după adăugarea celei de-a doua instrucțiui, codul ar deveni:

și ar fi funcționat așa cum ne-am dori.

Altfel de condiții

Spuneam că acea condiție este o expresie booleană. Până acum am folosit o simplă variabilă booleană; este ușor să ne gândim că putem folosi operatorii booleeni pe care i-am prezentat în episodul trecut. Poate dacă în caleașcă se află și un prinț, dorim de fapt să mergem la nunta din Împărăția Verde. Codul ar putea arăta așa:

Dar, de obicei condițiile pe care dorim să le verificăm sunt cu totul altele. Dacă lucrăm cu numere, am putea dori să verificăm dacă un număr are o anumită valoare, să vedem dacă un număr este mai mare decât altul etc.

Să considerăm un prim exemplu: avem o variabilă nr care conține un număr întreg; în cazul în care valoarea acelei variabile este 2457, dorim să scriem mesajul "Număr magic". Codul este:

Observăm că nu am scris un singur simbol =, ci două; == este operatorul de egalitate. Folosim un singur = pentru atribuire. Secvența următoare nu este validă:

Operatorul de egalitate este aplicat asupra a doi operatori cu tipuri compatibile; pentru tipurile primitive nu avem nicio surpriză; rezultatul este o valoare booleană care arată dacă cele două valori sunt egale.

Avem și un operator de inegalitate; rezultatul arată dacă cele două valori nu sunt egale.

După cum vedeți, operatorul este !=. Intuitiv, am putea spune că am negat egalitatea; am folosit semnul exclamării (cel care indică negația) urmat de semnul egal.

Dar, numerele pot fi și comparate; nu ar trebui să ne surprindă deloc existența unor operatori ca < sau >. Să verificăm dacă un număr este strict pozitiv:

La fel de simplu este să verificăm dacă numărul este strict negativ:

Când comparăm numerele folosim câteodată și conceptul de mai mare sau egal. Avem și un operator pentru așa ceva; nu este chiar  cum poate ne-ar plăcea, ci >=. Să vedem cum verificăm dacă un număr este pozitiv (nu strict pozitiv):

Similar, avem operatorul <= pentru mai mic sau egal.

Condiții compuse

Rezultatul utilizării oricărui astfel de operator este o valoare booleană; este natural să ne așteptăm să putem folosi operatorii booleeni asupra rezultatelor.

Am putea dori să verificăm dacă un anumit număr întreg este format dintr-o singură cifră. Am avea o condiția dublă: numărul trebuie să fie mai mare sau egal cu zero și mai mic decât zece. Am putea scrie următorul cod:

Mai avem și alte variante; una ar putea fi:

Codul funcționează corect fiindcă operațiile de comparare sunt executate înaintea conjucției logice; există o ordine specifică limbajului și de data aceasta ordinea de convine; dar, ar fi cam mult să ținem minte ordinea luată în considerare de limbaj; de obicei vrem să ne asigurăm că operațiile sunt efectuate în ordinea pe care o dorim; pentru aceasta folosim paranteze, chiar dacă nu sunt neapărat necesare; codul poate fi înțeles mult mai ușor. Așadar, varianta recomandată ar fi:

Avem paranteze în paranteze, dar totul este în regulă; totuși, să nu vă gândiți că am putea folosi paranteze drepte sau acolade; am întâlnit deja acolade și am văzut că au cu totul alt rol (delimitează grupuri de instrucțiuni); vom întâlni și paranteze drepte, care au și ele un rol diferit.

Deși nu prea are sens să scriem un astfel de cod, pentru a ilustra că nu tot timpul ordinea este cea pe care am dori-o să verificăm dacă un număr este diferit del 2457 verificând egalitatea și negând rezultatul. Codul ar arăta așa:

Vom observa că este generată o eroare care ne spune că operatorul logic de negație nu poate fi aplicat asupra unui număr întreg. Totul se rezolvă dacă scriem:

În Scala mai avem la dispoziție doi operatori pentru condițiile compuse care ar putea fi utili. Este vorba de && și ||. Ei seamănă cu  & și |, dar există o șmecherie. Dacă avem && și prima dintre cele două condiții este falsă, atunci a doua nu mai este verificată fiindcă rezultatul ar fi false oricum. Similar, dacă avem || și prima dintre cele două condiții este adevărată, atunci a doua nu mai este verificată fiindcă rezultatul ar fi true oricum. Deocamdată nu pare foarte important; dar, dacă ne gândim că aceste condiții pot deveni din ce în ce mai complicate, ne putem imagina că am putea ajunge să economisim timp dacă nu facem verificări inutile. Să revenim la codul care verifică dacă un număr are o singură cifră, dar să dublăm ampersandul:

Dacă numărul ar fi negativ, prima condiție este falsă și nu mai are rost să verificăm dacă el este mai mic decât zece. Chiar dacă ar fi (și în cazul nostru știm că va fi fiindcă este negativ), nu are nicio relevanță; condiția compusă va fi falsă.

Alternativa

Să revenim la exemplul inițial; spuneam că dacă avem o prințesă în caleașcă vrem să mergem în Împărăția Roșie. Probabil că dacă nu însoțim nicio prințesă am vrea totuși să mergem în Împărăția Verde (poate găsim o prințesă acolo?). Pentru a verifica dacă trebuie să mergem în Împărăția Verde, am putea nega condiția; codul ar arăta astfel:

Deocamdată nu avem nicio problemă, dar pare complicat și ne putem imagina că în situații complexe ar deveni din ce în ce mai ciudat. Codul pe care l-am scris ar putea fi descris astfel: dacă în caleașcă este o prințesă mergem în Împărăția Roșie; dacă în caleașcă nu este o prințesă mergem în Împărăția Verde. Pare puțin ciudat și cam lung; ne-am aștepta ca raționamentul să fie ceva de genul: dacă în caleașcă este o prințesă mergem în Împărăția Roșie; altfel mergem în Împărăția Verde. Din fericire, Scala și majoritatea limbajelor de programare ne permit să "spunem" altfel. Cuvântul va fi în engleză: else.

În Scala vom avea cuvântul if, urmat de condiția care trebuie verificată, cuprinsă între paranteze rotunde, de instrucțiunea care trebuie executată în cazul în care condiția este îndeplinită, apoi de cuvântul else și de instrucțiunea care trebuie executată în cazul în care condiția este îndeplinită.

Codul nostru devine:

If în if

Am spus că în cazul în care este îndeplinită o condiție se execută o instrucțiune. Am mai arătat că dacă nu este îndeplinită condiția, putem specifica o altă instrucțiune care să fie executată. Nu am pus nicio restricție legată de tipul instrucțiunilor respective. De fapt, nu există nicio restricție. Am văzut că putem pune o instrucțiune simplă sau un bloc de instrucțiuni. Dar, putem pune chiar și o altă instrucțiune condițională. De fapt, și printre instrucțiunile care formează un bloc, oricare poate fi un if.

Așadar, putem verifica o condiție și apoi, printre operațiile pe care le efectuăm, putem verifica o altă condiție și efectua alte operații în funcție de ea.

Să extindem puțin exemplul inițial; la răscrucea de drumuri putem alege trei variante: spre Împărăția Roșie, spre Împărăția Verde sau spre Împărăția Albastră. În caleașcă putem avea sau nu o prințesă și putem sau nu avea un prinț. Dacă prințesa nu este în caleașcă, atunci mergem în Împărăția Albastră să aducem prințesa; dacă e în caleașcă și nu e însoțită, mergem în Împărăția Verde să luăm prințul; dacă e însoțită de prinț, mergem la nuntă în Împărăția Roșie.

Așadar, vom avea o condiția care verifică dacă prințesa există. Dacă da, atunci trebuie să verificăm dacă există și prințul. Dacă prințul există, scriem mesajul "Împărăția Roșie". Dacă prințul nu există, scriem mesajul "Împărăția Verde". Dacă prințesa nu există, scriem mesajul "Împărăția Albastră" indiferent dacă prințul există sau nu.

Codul ar putea arăta așa:

De data aceasta putem și fără acolade, deși nu este recomandat:

Nu trebuie neapărat să ne oprim la un singur if în if. Cel din interior poate conține la rândul său o instrucțiune condițională; la fel acest al treilea if și așa mai departe.

Când avem două sau mai multe astfel de instrucțiuni if, spunem că ele sunt imbricate.

Să vedem de ce este periculos să nu folosim acolade. Poate nu vrem să facem absolut nimic în cazul în care avem prințesă dar nu avem prinț. Am putea crede că este suficient să eliminăm liniile 4 și 5. Codul ar deveni:

Vă mai aduceți aminte cum calculatoarele fac ceea ce le spunem să facă, nu ceea ce vrem noi să facă? Degeaba avem acel else la începutul liniei 4; calculatorul nu are cum să știe că noi vrem ca acesta să fie alternativa pentru primul if; caracterele albe sunt ignorate; e ca și cum am scrie:

Cum alege calculatorul? De data aceasta nu cum vrem noi! Un else reprezintă alternativa pentru cel mai apropiat if.

Dacă am fi transformat codul care avea acolade, noua versiune ar fi fost:

De data aceasta obținem ce ne-am dorit; datorită faptului că al doilea if  face parte dintr-un bloc și acel bloc s-a încheiat, alternativa nu se poate aplica decât primului if.

Va urma

În următorul episod vom vedea ce opțiuni avem la dispoziție în cazul în care o anumită variabilă (sau expresie) poate avea diferite valori și trebuie să executăm diverse instrucțiuni în funcție de fiecare valoare în parte.