Programarea funcțională explicată în 5 minute [With Examples]

Dezvoltarea de software reprezintă o activitate complexă și solicitantă din punct de vedere tehnic, ce impune o planificare riguroasă și o strategie bine definită pentru a determina cea mai adecvată metodă de a aborda o problemă prin intermediul programării.

În acest context, este esențial să se analizeze paradigma de programare potrivită înainte de a începe dezvoltarea oricărui software.

O paradigmă de programare este un model sau o abordare fundamentală a programării, care oferă un set de caracteristici, principii, reguli și stiluri pentru proiectarea, structurarea și scrierea codului sursă al programelor de calculator.

Printre exemplele cunoscute de paradigme de programare se numără programarea orientată pe obiecte (POO), programarea procedurală, programarea bazată pe evenimente și programarea funcțională.

Programarea funcțională, în mod special, a atras un interes considerabil în ultimii ani, datorită promisiunii unui cod mai concis, cu mai puține erori, care este, de asemenea, ușor de reutilizat și de întreținut. Dar ce înseamnă, de fapt, programarea funcțională?

Programarea funcțională este o subcategorie a paradigmei de programare declarativă. Programarea declarativă este o abordare în care codul se concentrează pe descrierea rezultatului dorit, mai degrabă decât pe specificarea pașilor exacți necesari pentru a obține acel rezultat.

Un exemplu relevant este interogarea bazelor de date SQL. În loc să definim modul exact de preluare a datelor, specificăm doar informațiile pe care dorim să le obținem.

Programarea funcțională este, așadar, o metodologie de construire a programelor, care utilizează expresii și funcții pure, aplicate secvențial pentru a rezolva probleme sau pentru a obține rezultatele dorite.

În programarea funcțională, funcționalitatea unui program este segmentată în funcții pure, reutilizabile, fiecare cu o singură responsabilitate. Întreaga activitate a programului se realizează prin intermediul acestor funcții.

O funcție pură este o funcție deterministă care, la primirea acelorași valori de intrare, va genera mereu același rezultat și nu va modifica alte părți ale aplicației.

Rezultatul unei funcții pure depinde exclusiv de valorile sale de intrare și nu de variabile globale care ar putea influența ieșirea funcției.

Aceste funcții preiau valori de intrare, le procesează local și produc o ieșire, fără a afecta alte părți ale programului.

Programarea funcțională folosește date imuabile, adică date care nu pot fi modificate după crearea lor, și evită stările partajate, în care aceleași date pot fi accesate și modificate de diverse componente ale unui program.

Deoarece programarea funcțională se bazează în mare măsură pe funcții, acestea sunt considerate „cetățeni de primă clasă”, ceea ce înseamnă că pot fi transmise ca argumente, stocate în variabile și returnate de alte funcții.

În plus, programarea funcțională preferă expresiile în locul instrucțiunilor și, prin urmare, evită structurile repetitive, precum buclele `for` și `while`. Această abordare simplifică logica programului și ușurează depanarea.

Tipuri de limbaje de programare funcționale

Există două categorii principale de limbaje de programare funcționale:

  • Limbaje pur funcționale – Aceste limbaje susțin, impun și promovează utilizarea paradigmelor funcționale, cum ar fi funcțiile pure de primă clasă, imuabilitatea stărilor și a datelor și funcțiile fără efecte secundare. Exemple de limbaje pur funcționale sunt Haskell, Agda, Clean, Idris, Futhark și Elm.
  • Limbaje funcționale impure – Aceste limbaje oferă suport pentru paradigmele programării funcționale, dar permit și utilizarea de funcții impure, modificări ale stării programului și operații cu efecte secundare. Exemple de limbaje funcționale impure includ Javascript, Rust, Erlang, Python, Ruby, Java, Kotlin și Clojure.

Atât limbajele pur funcționale, cât și cele impure sunt utilizate de programatori. Cu toate acestea, tranziția la un limbaj pur funcțional poate necesita timp și efort semnificativ dacă nu ați mai folosit programarea funcțională.

Limbaje și biblioteci de programare funcționale

Câteva exemple populare de limbaje și biblioteci de programare funcționale:

#1. Haskell

Haskell este un limbaj de programare pur funcțional, cu tip static și evaluare leneșă, considerat a fi chintesența paradigmei de programare funcțională.

Pe lângă inferența de tip, limbajul permite evaluarea leneșă, în care expresiile sunt evaluate doar atunci când rezultatele lor sunt necesare. Haskell oferă, de asemenea, suport pentru programarea concurentă, iar compilatorul său include un colector de gunoi eficient și o bibliotecă de concurență ușoară.

Prin aplicarea strictă a principiilor programării funcționale, Haskell simplifică dezvoltarea sistemelor software complexe și facilitează întreținerea acestora.

În industrie, Haskell este adesea limbajul preferat pentru crearea sistemelor autonome sau a limbajelor specifice unui domeniu. Este, de asemenea, utilizat pe scară largă în mediul academic și în cercetare. Companii precum Microsoft, Github, Hasura și Lumi folosesc Haskell în proiectele lor.

#2. Ramda

Ramda este o bibliotecă de programare funcțională pentru limbajul JavaScript. Ramda simplifică crearea logicii complexe prin compoziția funcțională și oferă un set de funcții utilitare care încurajează și susțin principiile programării funcționale în JavaScript.

Ramda facilitează utilizarea obiectelor și a funcțiilor imuabile, fără efecte secundare, care sunt concepte cheie în programarea funcțională.

Deoarece JavaScript nu este un limbaj pur funcțional, utilizarea unei biblioteci precum Ramda permite programarea funcțională și beneficiile sale de performanță în contextul JavaScript.

#3. Elixir

Elixir este un limbaj de programare funcțional, concurent și de uz general, conceput pentru a fi scalabil, ușor de întreținut și tolerant la erori. Creat în 2011 de Jose Valim, limbajul rulează pe mașina virtuală BEAM și este utilizat de companii precum Heroku, Discord, change.org și Duffel.

Ca limbaj de programare funcțional, Elixir promovează imuabilitatea stărilor și a datelor, utilizarea funcțiilor pure și transformarea datelor.

Concepte cheie în programarea funcțională

#1. Funcții pure

Programarea funcțională folosește extensiv funcțiile pure. Acestea au două caracteristici principale: pentru aceleași valori de intrare, produc întotdeauna aceeași ieșire, fiind deterministe, și nu au efecte secundare, adică nu modifică mediul extern în afara domeniului lor.

Câteva exemple de funcții pure includ:

//funcție pentru calculul pătratului unui număr
function square(x) {
    return x * x;
}

//funcție pentru adunarea a două variabile
function add(a, b) {
    return a + b
}

Funcțiile de mai sus returnează aceeași ieșire pentru aceleași intrări și nu au efecte secundare în afara domeniului lor.

#2. Imuabilitate

În programarea funcțională, datele sunt imuabile. Aceasta înseamnă că, odată ce o variabilă a fost inițializată, aceasta nu poate fi modificată, asigurând păstrarea stării acesteia pe tot parcursul programului.

Pentru a efectua o operație care modifică o variabilă, se creează o nouă variabilă, care stochează datele actualizate, fără a schimba variabila inițială.

#3. Funcții de ordin superior

Funcțiile de ordin superior sunt funcții care acceptă una sau mai multe funcții ca argumente și/sau returnează o altă funcție.

Aceste funcții sunt utile în programarea funcțională, deoarece permit combinarea mai multor funcții pentru a crea funcții noi, utilizarea apelurilor inverse, abstractizarea modelelor comune în funcții reutilizabile și scrierea unui cod mai concis și expresiv.

Un exemplu de funcție de ordin superior:

// O funcție de ordin superior care returnează o funcție care înmulțește
// un număr cu un factor dat
function multiplier(factor) {
    return function (number) {
      return number * factor;
    }
  }
  
const double = multiplier(2); 
const triple = multiplier(3);
const quadruple = multiplier(4);
  
console.log(double(5)); // Ieșire: 10
console.log(triple(5)); // Ieșire: 15
console.log(quadruple(5)); // Ieșire: 20

#4. Recursiune

Programarea funcțională preferă expresiile în detrimentul instrucțiunilor, evitând instrucțiunile de control al fluxului, cum ar fi buclele `for` și `while`. Acestea sunt înlocuite cu recursiunea, utilizată pentru a realiza iterații în această paradigmă.

Recursiunea implică autoapelarea repetată a unei funcții, până când se îndeplinește o condiție de ieșire. O problemă complexă este divizată în subprobleme mai simple, rezolvate recursiv până la atingerea unui caz de bază, oferind soluția problemei inițiale.

#5. Programare Declarativă

Programarea funcțională este o sub-paradigmă a programării declarative, care se concentrează pe scrierea codului în termeni de „ce” trebuie făcut, nu „cum”.

În acest sens, codul ar trebui să descrie rezultatul dorit sau problema care trebuie rezolvată. Modul exact de realizare depinde de limbajul de programare utilizat, ceea ce duce la un cod mai concis și ușor de înțeles.

#6. Fără stare

Programarea funcțională pune accent pe codul fără stare, unde codul nu menține o stare globală care poate fi modificată de funcții. Rezultatele funcțiilor depind exclusiv de valorile de intrare și nu sunt influențate de alte părți ale codului.

Funcțiile nu modifică o stare sau o variabilă din program aflată în afara domeniului lor.

#7. Execuție paralelă

Datorită utilizării datelor imuabile și a funcțiilor pure, programarea funcțională permite executarea paralelă a mai multor calcule simultan.

Fiecare funcție se ocupă doar de valorile sale de intrare, fără a se preocupa de efectele secundare din altă parte a programului. Problemele complexe pot fi segmentate în subprobleme mai mici și executate simultan, ceea ce îmbunătățește performanța și eficiența.

Beneficiile programării funcționale

Câteva beneficii ale programării funcționale:

Mai puține erori software

Pe lângă faptul că este mai ușor de citit și înțeles, codul care implementează programarea funcțională este mai puțin predispus la erori.

Deoarece programarea funcțională folosește date imuabile, nu există mai multe componente ale programului care să modifice starea unei variabile. Acest lucru reduce erorile cauzate de modificarea datelor din mai multe locații din cauza stărilor partajate.

Îmbunătățește lizibilitatea codului

Programarea funcțională, ca sub-paradigmă a programării declarative, pune accent pe descrierea a ceea ce trebuie făcut, nu a modului în care se face. Acest lucru, împreună cu funcțiile pure, duce la un cod auto-explicativ, mai ușor de citit, înțeles și întreținut.

Îmbunătățește reutilizarea codului

Programarea funcțională implică segmentarea problemelor complexe în subprobleme, rezolvate cu funcții pure. Aceste funcții pot fi ușor combinate și reutilizate, facilitând scrierea de cod reutilizabil.

Testare și depanare mai ușoară

Programarea funcțională utilizează funcții pure, fără efecte secundare, care depind doar de valorile de intrare și produc rezultate deterministe.

Această abordare simplifică testarea și depanarea, deoarece nu este necesar să se urmărească modificările variabilelor în diverse părți ale programului.

Datorită lipsei dependențelor, se pot viza anumite componente ale programului în timpul testării și depanării.

Suportă concurență și paralelism

Programarea funcțională promovează utilizarea de funcții pure și date imuabile, permițând execuția sigură a mai multor funcții în paralel. Această abilitate de a rula simultan mai multe operațiuni îmbunătățește viteza de procesare și utilizarea procesoarelor cu mai multe nuclee.

Ca paradigmă, programarea funcțională ajută la scrierea unui cod mai ușor de citit și de înțeles, cu mai puține erori, și oferă suport excelent pentru paralelism, optimizând utilizarea procesoarelor multicore. Astfel, se pot construi sisteme software mai fiabile și mai ușor de scalat.

Limitările programării funcționale

Deși programarea funcțională are multe de oferit, aceasta necesită un efort considerabil pentru a învăța paradigma și conceptele sale. Aceasta introduce noi modalități de structurare a codului și concepte de programare.

Codul folosind programarea funcțională poate deveni complex, deoarece nu se utilizează structuri intuitive, cum ar fi buclele `for` și `while`. Scrierea de programe recursive poate fi dificilă.

Programatorii pot avea nevoie de timp pentru a stăpâni programarea funcțională, mai ales dacă provin din limbaje care folosesc stări mutabile, cum ar fi programarea orientată pe obiecte.

O altă limitare a programării funcționale decurge din principiul imuabilității. Deoarece datele și stările sunt imuabile, se creează noi structuri de date în loc de modificări, ceea ce duce la o utilizare mai intensivă a memoriei. Această natură imuabilă poate duce, de asemenea, la o performanță mai slabă în unele aplicații.

Concluzie

Deși programarea funcțională există de mult timp, a devenit o paradigmă populară recent. Chiar dacă poate fi dificil de abordat inițial, programatorii pot beneficia enorm de cunoștințele despre programarea funcțională.

Nu este obligatoriu să utilizați limbaje pur funcționale, cum ar fi Haskell, pentru a implementa conceptele programării funcționale. Acestea pot fi aplicate și în limbaje precum Javascript, Java, Python și Kotlin, obținând beneficiile programării funcționale în proiecte.

Pentru a începe, puteți explora diverse resurse disponibile pentru învățarea limbajului Python, inclusiv resurse special create pentru începători.