Introducere în programarea funcțională: Paradigme JavaScript
Publicat: 2022-03-11Programarea funcțională este o paradigmă de construire a programelor de calculator folosind expresii și funcții fără a modifica starea și datele.
Prin respectarea acestor restricții, programarea funcțională își propune să scrie cod mai clar de înțeles și mai rezistent la erori. Acest lucru se realizează prin evitarea utilizării instrucțiunilor de control al fluxului ( for
, while
, break
, continue
, goto
) care fac codul mai greu de urmărit. De asemenea, programarea funcțională ne cere să scriem funcții pure, deterministe, care sunt mai puțin susceptibile de a avea erori.
În acest articol, vom vorbi despre programarea funcțională folosind JavaScript. Vom explora, de asemenea, diverse metode și caracteristici JavaScript care fac posibil acest lucru. În cele din urmă, vom explora diferite concepte asociate cu programarea funcțională și vom vedea de ce sunt atât de puternice.
Înainte de a intra în programarea funcțională, totuși, trebuie să înțelegem diferența dintre funcțiile pure și impure.
Funcții pure vs. impure
Funcțiile pure iau o anumită intrare și oferă o ieșire fixă. De asemenea, nu provoacă efecte secundare în lumea exterioară.
const add = (a, b) => a + b;
Aici, add
este o funcție pură. Acest lucru se datorează faptului că, pentru o valoare fixă a lui a
și b
, ieșirea va fi întotdeauna aceeași.
const SECRET = 42; const getId = (a) => SECRET * a;
getId
nu este o funcție pură. Motivul este că folosește variabila globală SECRET
pentru a calcula rezultatul. Dacă SECRET
s-ar modifica, funcția getId
va returna o valoare diferită pentru aceeași intrare. Astfel, nu este o funcție pură.
let id_count = 0; const getId = () => ++id_count;
Aceasta este, de asemenea, o funcție impură și asta din câteva motive: (1) folosește o variabilă non-locală pentru a-și calcula ieșirea și (2) creează un efect secundar în lumea exterioară prin modificarea unei variabile în aceeași funcție. lume.
Acest lucru poate fi supărător dacă ar trebui să depanăm acest cod.
Care este valoarea curentă a lui id_count
? Ce alte funcții modifică id_count
? Există și alte funcții care se bazează pe id_count
?
Din aceste motive, folosim doar funcții pure în programarea funcțională.
Un alt avantaj al funcțiilor pure este că pot fi paralelizate și memorate. Aruncă o privire la cele două funcții anterioare. Este imposibil să le paralelizezi sau să le memorezi. Acest lucru ajută la crearea unui cod performant.
Principiile programării funcționale
Până acum, am aflat că programarea funcțională depinde de câteva reguli. Ele sunt după cum urmează.
- Nu modificați datele
- Utilizați funcții pure: ieșire fixă pentru intrări fixe și fără efecte secundare
- Folosiți expresii și declarații
Când îndeplinim aceste condiții, putem spune că codul nostru este funcțional.
Programare funcțională în JavaScript
JavaScript are deja unele funcții care permit programarea funcțională. Exemplu: String.prototype.slice, Array.protoype.filter, Array.prototype.join.
Pe de altă parte, Array.prototype.forEach, Array.prototype.push sunt funcții impure.
Se poate argumenta că Array.prototype.forEach
nu este o funcție impură prin design, dar gândiți-vă la asta - nu este posibil să faceți nimic cu ea, cu excepția mutației datelor non-locale sau a efectelor secundare. Astfel, este în regulă să-l încadrezi în categoria funcțiilor impure.
De asemenea, JavaScript are o declarație const, care este perfectă pentru programarea funcțională, deoarece nu vom muta nicio dată.
Funcții pure în JavaScript
Să ne uităm la câteva dintre funcțiile (metodele) pure oferite de JavaScript.
Filtru
După cum sugerează și numele, aceasta filtrează matricea.
array.filter(condition);
Condiția de aici este o funcție care primește fiecare element al matricei și ar trebui să decidă dacă să păstreze elementul sau nu și să returneze valoarea booleană adevărată pentru asta.
const filterEven = x => x%2 === 0; [1, 2, 3].filter(filterEven); // [2]
Observați că filterEven
este o funcție pură. Dacă ar fi fost impur, atunci ar fi făcut ca întregul filtru să fie numit impur.
Hartă
map
fiecare element al matricei la o funcție și creează o nouă matrice pe baza valorilor returnate ale apelurilor de funcție.
array.map(mapper)
mapper
este o funcție care ia un element dintr-o matrice ca intrare și returnează rezultatul.
const double = x => 2 * x; [1, 2, 3].map(double); // [2, 4, 6]
Reduce
reduce
reduce matricea la o singură valoare.
array.reduce(reducer);
reducer
este o funcție care preia valoarea acumulată și următorul element din matrice și returnează noua valoare. Se numește astfel pentru toate valorile din matrice, una după alta.
const sum = (accumulatedSum, arrayItem) => accumulatedSum + arrayItem [1, 2, 3].reduce(sum); // 6
Concat
concat
adaugă elemente noi la o matrice existentă pentru a crea o matrice nouă. Este diferit de push()
în sensul că push()
modifică datele, ceea ce le face impure.

[1, 2].concat([3, 4]) // [1, 2, 3, 4]
Puteți face același lucru folosind operatorul de răspândire.
[1, 2, ...[3, 4]]
obiect.atribuie
Object.assign
copie valorile de la obiectul furnizat la un obiect nou. Deoarece programarea funcțională se bazează pe date imuabile, o folosim pentru a crea obiecte noi bazate pe obiecte existente.
const obj = {a : 2}; const newObj = Object.assign({}, obj); newObj.a = 3; obj.a; // 2
Odată cu apariția ES6, acest lucru se poate face și folosind operatorul de răspândire.
const newObj = {...obj};
Crearea propriei funcții pure
Ne putem crea și funcția pură. Să facem una pentru duplicarea unui șir de n
de ori.
const duplicate = (str, n) => n < 1 ? '' : str + duplicate(str, n-1);
Această funcție dublează un șir de n
ori și returnează un șir nou.
duplicate('hooray!', 3) // hooray!hooray!hooray!
Funcții de ordin superior
Funcțiile de ordin superior sunt funcții care acceptă o funcție ca argument și returnează o funcție. Adesea, ele sunt folosite pentru a adăuga la funcționalitatea unei funcții.
const withLog = (fn) => { return (...args) => { console.log(`calling ${fn.name}`); return fn(...args); }; };
În exemplul de mai sus, creăm o funcție de ordin superior withLog
care preia o funcție și returnează o funcție care înregistrează un mesaj înainte ca funcția încapsulată să ruleze.
const add = (a, b) => a + b; const addWithLogging = withLog(add); addWithLogging(3, 4); // calling add // 7
withLog
HOF poate fi folosit și cu alte funcții și funcționează fără conflicte sau fără a scrie cod suplimentar. Aceasta este frumusețea unui HOF.
const addWithLogging = withLog(add); const hype = s => s + '!!!'; const hypeWithLogging = withLog(hype); hypeWithLogging('Sale'); // calling hype // Sale!!!
Se poate numi și fără a defini o funcție de combinare.
withLog(hype)('Sale'); // calling hype // Sale!!!
curry
Curry înseamnă defalcarea unei funcții care preia mai multe argumente într-unul sau mai multe niveluri de funcții de ordin superior.
Să luăm funcția de add
.
const add = (a, b) => a + b;
Când trebuie să-l curățăm, îl rescriem distribuind argumentele pe mai multe niveluri, după cum urmează.
const add = a => { return b => { return a + b; }; }; add(3)(4); // 7
Beneficiul curry-ului este memorarea. Acum putem memoriza anumite argumente într-un apel de funcție, astfel încât acestea să poată fi reutilizate mai târziu fără duplicare și recalculare.
// assume getOffsetNumer() call is expensive const addOffset = add(getOffsetNumber()); addOffset(4); // 4 + getOffsetNumber() addOffset(6);
Acest lucru este cu siguranță mai bun decât folosirea ambelor argumente peste tot.
// (X) DON"T DO THIS add(4, getOffsetNumber()); add(6, getOffsetNumber()); add(10, getOffsetNumber());
De asemenea, putem reformata funcția noastră curry pentru a arăta succint. Acest lucru se datorează faptului că fiecare nivel al apelului funcției currying este o instrucțiune de returnare pe o singură linie. Prin urmare, putem folosi funcții săgeți în ES6 pentru al refactoriza după cum urmează.
const add = a => b => a + b;
Compoziţie
În matematică, compoziția este definită ca trecerea ieșirii unei funcții în intrarea alteia, astfel încât să creeze o ieșire combinată. Același lucru este posibil în programarea funcțională, deoarece folosim funcții pure.
Pentru a arăta un exemplu, să creăm câteva funcții.
Prima funcție este interval, care ia un număr de început a
și un număr final b
și creează o matrice formată din numere de la a
la b
.
const range = (a, b) => a > b ? [] : [a, ...range(a+1, b)];
Apoi avem o funcție de multiplicare care ia o matrice și înmulțește toate numerele din ea.
const multiply = arr => arr.reduce((p, a) => p * a);
Vom folosi aceste funcții împreună pentru a calcula factorial.
const factorial = n => multiply(range(1, n)); factorial(5); // 120 factorial(6); // 720
Funcția de mai sus pentru calculul factorial este similară cu f(x) = g(h(x))
, demonstrând astfel proprietatea compoziției.
Cuvinte de încheiere
Am trecut prin funcții pure și impure, programare funcțională, noile caracteristici JavaScript care ajută cu aceasta și câteva concepte cheie în programarea funcțională.
Sperăm că această piesă vă trezește interesul pentru programarea funcțională și, eventual, vă motivează să o încercați în codul dvs. Suntem convinși că va fi o experiență de învățare și o piatră de hotar în călătoria dvs. de dezvoltare software.
Programarea funcțională este o paradigmă bine cercetată și robustă de scriere a programelor de calculator. Odată cu introducerea ES6, JavaScript permite o experiență de programare funcțională mult mai bună decât oricând.