Funcțiile și posibilitățile lor în programare. Programarea funcțională se bazează pe calculul lambda. Funcții de ordin superior

Funcțiile sunt abstracții, în care detaliile de implementare ale unei acțiuni sunt ascunse în spatele unui nume separat. Un set bine scris de funcții vă permite să le utilizați de mai multe ori. Biblioteca standard Python vine cu o mulțime de funcții out-of-the-box și bine unse, dintre care multe sunt suficient de generice pentru a funcționa cu o mare varietate de intrări. Chiar dacă o anumită bucată de cod nu este folosită de mai multe ori, dar este destul de autonomă în ceea ce privește datele de intrare și de ieșire, poate fi separată în siguranță într-o funcție separată.

Această prelegere se concentrează mai mult pe considerații practice decât pe teoria programării funcționale. Cu toate acestea, termenii corespunzători vor fi utilizați și explicați acolo unde este cazul.

În continuare, descrierea și utilizarea funcțiilor în Python, recursiunea, funcțiile de trecere și returnare ca parametri, secvențe de procesare și iteratoare, precum și un astfel de concept ca generator vor fi luate în considerare în detaliu. Se va demonstra că în Python, funcțiile sunt obiecte (și astfel pot fi trecute ca parametri și returnate ca rezultat al executării funcțiilor). În plus, vom vorbi despre cum puteți implementa unele mecanisme de programare funcționale care nu au suport sintactic direct în Python, dar sunt utilizate pe scară largă în limbaje de programare funcționale.

Ce este programarea funcțională?

Programare functionala este un stil de programare care folosește numai compoziții de funcții. Cu alte cuvinte, asta programareîn expresii, nu în comenzi imperative.

După cum subliniază David Mertz în articolul său despre programarea funcțională în Python, „functional programare - programareîn limbaje funcționale (LISP, ML, OCAML, Haskell, ...)", ale căror principale atribute sunt:

  • „Prezența funcțiilor de primă clasă” (funcțiile, împreună cu alte obiecte, pot fi trecute în interiorul funcțiilor).
  • Recursiunea este de bază structura de conducereîntr-un program.
  • Prelucrare liste (secvențe).
  • Interzicerea efectelor secundare în funcții, ceea ce înseamnă în primul rând nicio atribuire (în limbaje funcționale „pure”)
  • Interdicția operatorului, accentul este pus pe expresii. În loc de declarații, întregul program este în mod ideal o singură expresie cu definiții însoțitoare.
  • Întrebare cheie: ce trebuie calculat, nu Cum.
  • Utilizarea funcțiilor de ordin superior (funcții peste funcții peste funcții).

Program funcțional

În matematică, funcția afișează obiecte dintr-un set ( seturi de definiții de funcție) altcuiva ( set de valori ale funcției). Funcții matematice (se numesc curat) „mecanic”, calculați fără ambiguitate rezultatul având în vedere argumentele date. Funcțiile pure nu ar trebui să stocheze date între două apeluri. Poți să le consideri cutii negre, despre care știi doar ce fac, dar nu contează cum.

Programele de stil funcțional sunt concepute ca compoziţie funcții. În același timp, funcțiile sunt înțelese aproape în același mod ca și în matematică: ele mapează un obiect cu altul. În programare, funcțiile „pure” sunt un ideal care nu este întotdeauna realizabil în practică. Practic caracteristici utile au de obicei efect secundar: păstrați starea între apeluri sau modificați starea altor obiecte. De exemplu, este imposibil să ne imaginăm funcții I/O fără efecte secundare. De fapt, astfel de funcții sunt folosite de dragul acestor „efecte”. În plus, funcțiile matematice funcționează cu ușurință cu obiecte care necesită o cantitate infinită de informații (de exemplu, numere reale). În general, computerul

Programarea funcțională este o ramură a matematicii discrete și o paradigmă de programare în care procesul de calcul este interpretat ca calcul al valorilor funcțiilor în înțelegerea matematică a acestora din urmă (spre deosebire de funcțiile ca subrutine în programarea procedurală).

În contrast cu paradigma programării imperative, care descrie procesul de calcul ca o schimbare succesivă a stărilor (într-un sens similar cu cel din teoria automatelor). Dacă este necesar, în programarea funcțională, întregul set de stări secvențiale proces de calcul reprezentate în mod explicit, cum ar fi o listă.

Programarea funcțională se referă la calcularea rezultatelor funcțiilor din datele de intrare și a rezultatelor altor funcții și nu implică stocarea explicită a stării programului. În consecință, nici nu implică mutabilitatea acestei stări (spre deosebire de cea imperativă, unde unul dintre conceptele de bază este o variabilă care stochează valoarea acesteia și vă permite să o schimbați pe măsură ce algoritmul este executat).

În practică, diferența dintre o funcție matematică și conceptul de „funcție” în programarea imperativă este că funcțiile imperative se pot baza nu numai pe argumente, ci și pe starea variabilelor externe funcției și, de asemenea, au efecte secundare și modifică starea variabilelor externe. Astfel, în programarea imperativă, la apelarea aceleiași funcție cu aceiași parametri, dar în etape diferite ale execuției algoritmului, puteți obține date de ieșire diferite datorită influenței stării variabilei asupra funcției. Și într-un limbaj funcțional, atunci când apelăm o funcție cu aceleași argumente, obținem întotdeauna același rezultat: ieșirea depinde doar de intrare. Acest lucru permite runtimelor de limbaj funcțional să memoreze în cache rezultatele funcțiilor și să le apeleze într-o ordine nedeterminată de algoritm. (vezi mai jos funcțiile pure)



λ-calcul sunt baza pentru programarea funcțională, multe limbaje funcționale poate fi privit ca o „suprastructură” peste ele.

Puncte forte

[edit]Îmbunătățirea fiabilității codului

Partea atractivă a calculului fără stat este fiabilitatea crescută a codului datorită structurii clare și absenței necesității de a urmări efectele secundare. Orice funcție funcționează numai cu date locale și funcționează întotdeauna cu ele în același mod, indiferent de unde, cum și în ce circumstanțe este numită. Imposibilitatea mutarii datelor atunci când le sunt folosite în locuri diferite programul elimină apariția erorilor greu de detectat (cum ar fi atribuirea accidentală a unei valori incorecte unei variabile globale într-un program imperativ).

[editează] Ușurința organizării testării unitare

Deoarece o funcție din programarea funcțională nu poate produce efecte secundare, obiectele nu pot fi modificate atât în ​​interiorul domeniului, cât și în exterior (spre deosebire de programele imperative, unde o funcție poate seta o variabilă externă citită de a doua funcție). Singurul efect al evaluării unei funcții este rezultatul pe care îl returnează, iar singurul factor care afectează rezultatul este valoarea argumentelor.

Astfel, este posibil să testați fiecare funcție dintr-un program prin simpla evaluare a acesteia din diferite seturi de valori ale argumentelor. În acest caz, nu trebuie să vă faceți griji cu privire la apelarea funcțiilor în ordinea corectă, nici despre formarea corectă a stării exterioare. Dacă vreo funcție din program trece testele unitare, atunci puteți fi sigur de calitatea întregului program. În programele imperative, verificarea valorii returnate a unei funcții nu este suficientă: funcția se poate modifica starea externă, care, de asemenea, trebuie verificat, ceea ce nu trebuie făcut în programe functionale Oh.

[editează] Compilați opțiunile de optimizare

menţionată în mod tradiţional caracteristică pozitivă programarea funcțională este aceea că vă permite să descrieți programul în așa-numita formă „declarativă”, atunci când o succesiune rigidă de efectuare a multor operații necesare calculării rezultatului nu este setată în mod explicit, ci se formează automat în procesul de calcul al funcțiilor. [sursa nespecificata 1064 zile ] Această împrejurare, precum și absența stărilor, face posibilă aplicarea la programe funcționale suficient de metode complexe optimizare automată.

[editează] Paralelism

Un alt avantaj al programelor funcționale este că oferă cele mai largi posibilitati pentru paralelizarea automată a calculelor. Deoarece absența efectelor secundare este garantată, în orice apel de funcție, este întotdeauna permis să se evalueze două diverse opțiuni- ordinea evaluării acestora nu poate afecta rezultatul apelului.

[editare] Dezavantaje

Dezavantajele programării funcționale provin din aceleași caracteristici. Absența sarcinilor și înlocuirea lor cu generarea de noi date duce la necesitatea alocării constante și eliberării automate a memoriei, prin urmare, în sistemul de execuție al unui program funcțional, un colector de gunoi foarte eficient devine o componentă indispensabilă. Modelul de calcul non-strict duce la o ordine imprevizibilă a apelurilor de funcții, ceea ce creează probleme în I/O, unde ordinea operațiilor este importantă. De asemenea, evident, funcțiile de intrare în lor formă naturală(de exemplu, getchar de la bibliotecă standard limbajul C) nu sunt puri pentru că se pot întoarce diverse sensuri pentru aceleași argumente și sunt necesare unele trucuri pentru a elimina acest lucru.

Pentru a depăși deficiențele programelor funcționale, deja primele limbaje de programare funcționale includeau nu numai instrumente pur funcționale, ci și mecanisme de programare imperativă (atribuirea, bucla, „PROGN implicit” erau deja în LISP). Utilizarea unor astfel de instrumente ne permite să rezolvăm unele probleme practice, dar înseamnă îndepărtarea de ideile (și avantajele) de programare funcțională și scrierea de programe imperative în limbaje funcționale.[sursa nespecificată 1064 zile] În limbajele funcționale pure, aceste probleme sunt rezolvate prin alte mijloace, de exemplu, în Haskell, I /O este implementat folosind monade - concept non-trivial împrumutat din teoria categoriilor.

Recursiunea coadă este un caz special de recursivitate în care apelul recursiv al unei funcții este ultima operație. Acest tip de recursivitate este remarcabil prin faptul că poate fi înlocuit cu ușurință prin iterație, care este implementată în multe compilatoare de optimizare. Când o funcție este apelată, computerul trebuie să-și amintească locul din care a fost apelată funcția (adresa de retur), astfel încât după ce se termină să revină și să continue execuția programului. În mod normal, adresa de retur este stocată pe stivă. Uneori ultima actiune după ce toate celelalte operațiuni s-au finalizat, apelează doar o funcție, eventual ea însăși, și returnează rezultatul. În acest caz, nu este nevoie să ne amintim adresa de retur, funcția nou apelată va returna rezultatul direct în locul în care a fost apelată funcția inițială. Recursiunea cozii este adesea folosită în programele scrise în limbaje de programare funcționale. Este firesc să exprimăm multe calcule în limbaje precum funcțiile recursive și posibilitatea înlocuire automată translatorul recursiunii cozii la iterație înseamnă că din punct de vedere al eficienței de calcul este egal cu codul echivalent scris în formă iterativă.

Creatorii limbajului funcțional Scheme, unul dintre dialectele lui Lisp, au apreciat atât de mult importanța recursiunii cozii, încât specificația limbajului prevedea ca fiecare traducător al acestui limbaj să implementeze fără greș optimizarea recursiunii cozii.

Funcție de ordin superior- o funcție care ia alte funcții ca argumente sau returnează o altă funcție ca rezultat. Uneori, funcțiile de ordin superior sunt numite funcționale, deși acest lucru nu este în întregime adevărat, un echivalent mai precis este un operator.

În limbajele de programare funcționale, toate funcțiile sunt funcții de ordin superior.

În practică, diferența dintre o funcție matematică și conceptul de „funcție” în programarea imperativă este că funcțiile imperative se pot baza nu numai pe argumente, ci și pe starea variabilelor externe funcției și, de asemenea, au efecte secundare și modifică starea variabilelor externe. Astfel, în programarea imperativă, la apelarea aceleiași funcție cu aceiași parametri, dar în etape diferite ale execuției algoritmului, puteți obține date de ieșire diferite datorită influenței stării variabilei asupra funcției. Și într-un limbaj funcțional, atunci când apelăm o funcție cu aceleași argumente, obținem întotdeauna același rezultat: ieșirea depinde doar de intrare. Acest lucru permite timpilor de execuție pentru programele în limbaje funcționale să memoreze în cache rezultatele funcțiilor și să le apeleze într-o ordine nedeterminată de algoritm și să le paralelizeze fără nicio muncă suplimentară din partea programatorului (care este furnizată de funcții fără efecte secundare - funcţii pure).

YouTube enciclopedic

    1 / 5

    Ce este programarea funcțională

    Matematică și constante / Introducere în programare Lecția 4 (JavaScript ES6)

    Programare reactivă și interfețe web moderne

    Alexander Chirtsov despre matematică în fizică

    Anna Andreeva. Rezolvarea problemelor olimpiadelor la matematică

    Subtitrări

Limbaje de programare funcționale

Versiunile inițiale care nu sunt încă complet funcționale atât ale Lisp, cât și ale APL au avut o contribuție specială la crearea și dezvoltarea programării funcționale. Versiunile ulterioare ale Lisp, cum ar fi Scheme și, de asemenea diverse opțiuni APL a susținut toate caracteristicile și conceptele unui limbaj funcțional.

De regulă, interesul pentru limbajele de programare funcționale, în special pentru cele pur funcționale, a fost mai mult științific decât comercial. Cu toate acestea, limbi notabile, cum ar fi Erlang, OCaml, Haskell, Scheme (după 1986), precum și specifice (statistică), Wolfram (matematică simbolică) și ( analiza financiară), și XSLT (XML) și-au găsit utilizare în industria de programare comercială. Limbajele declarative larg răspândite, cum ar fi SQL și Lex/Yacc, conțin unele elemente de programare funcțională, cum ar fi să fiți atenți la utilizarea variabilelor. Limbile foilor de calcul pot fi considerate și limbi funcționale, deoarece celulele foi de calcul este setată o serie de funcții, de obicei în funcție doar de alte celule, iar dacă doriți să modelați variabile, trebuie să apelați la capacitățile limbajului macro imperativ.

Poveste

Primul limbaj funcțional a fost Lisp, creat de John McCarthy în timpul mandatului său la sfârșitul anilor cincizeci și implementat inițial pentru IBM 700/7000. (Engleză) Rusă. Lisp a fost pionier în multe concepte de limbaj funcțional, deși limbajul folosește mai mult decât paradigma de programare funcțională. Dezvoltare în continuare Lisp a devenit limbi precum Scheme și Dylan.

Concepte

Unele concepte și paradigme sunt specifice programării funcționale și în mare parte străine de programare imperativă (inclusiv programarea orientată pe obiecte). Cu toate acestea, limbajele de programare sunt de obicei un hibrid al mai multor paradigme de programare, astfel încât limbajele de programare „în mare parte imperative” pot folosi oricare dintre aceste concepte. [ ]

Funcții de ordin superior

Funcțiile de ordin superior sunt funcții care pot lua drept argumente și pot returna alte funcții. Matematicienii numesc adesea o astfel de funcție operator, de exemplu, operatorul derivat sau operatorul de integrare.

Funcțiile de ordin superior permit utilizarea currying - transformarea unei funcții dintr-o pereche de argumente într-o funcție care își ia argumentele pe rând. Această transformare și-a primit numele în onoarea lui H. Curry.

Funcții pure

Funcțiile pure sunt cele care nu au efecte secundare I/O și memorie (depind doar de parametrii lor și returnează doar rezultatul). Funcțiile pure au mai multe proprietăți utile, dintre care multe pot fi folosite pentru a vă optimiza codul:

  • Dacă nu se utilizează rezultatul unei funcții pure, apelul acesteia poate fi eliminat fără a afecta alte expresii.
  • Rezultatul unui apel la o funcție pură poate fi memorat, adică stocat într-un tabel de valori împreună cu argumentele apelului. Dacă funcția este apelată ulterior cu aceleași argumente, rezultatul acesteia poate fi preluat direct din tabel fără a fi calculat (numit uneori principiul transparenței de referință). Memorizarea, cu prețul unei cantități mici de memorie, poate crește semnificativ performanța și poate reduce ordinea de creștere a unor algoritmi recursivi.
  • Dacă nu există dependență de date între două funcții pure, atunci ordinea evaluării lor poate fi modificată sau paralelizată (cu alte cuvinte, calculul funcții pure satisface principiile thread-safe)
  • Dacă întregul limbaj nu permite efecte secundare, atunci poate fi folosită orice politică de evaluare. Acest lucru oferă compilatorului libertatea de a combina și reorganiza evaluarea expresiilor din program (de exemplu, pentru a elimina structurile arborescente).

În timp ce majoritatea compilatorilor limbajelor de programare imperative recunosc funcțiile pure și elimină subexpresiile comune pentru apelurile de funcții pure, nu pot face întotdeauna acest lucru pentru bibliotecile precompilate, care în general nu oferă aceste informații. Unele compilatoare, cum ar fi gcc, oferă programatorului Cuvinte cheie pentru a desemna funcții pure. Fortran 95 vă permite să desemnați funcții ca „pure” (pure).

recursiunea

Funcțiile recursive pot fi generalizate la funcții de ordin superior folosind, de exemplu, catamorfismul și anamorfismul (sau „convoluția” și „expansiunea”). Funcțiile de acest fel joacă rolul unui astfel de concept ca ciclu în limbaje de programare imperative. [ ]

Abordarea evaluării argumentelor

Limbile funcționale pot fi clasificate în funcție de modul în care sunt gestionate argumentele funcției în timpul evaluării. Din punct de vedere tehnic, diferența constă în semantica denotațională a expresiei. De exemplu, cu o abordare strictă a calculului expresiei

imprimare (len ([ 2 + 1 , 3 * 2 , 1 / 0 , 5 - 4 ]))

rezultatul va fi o eroare, deoarece al treilea element al listei conține împărțirea la zero. Cu o abordare nestrictă, valoarea expresiei va fi 4, deoarece, strict vorbind, valorile elementelor sale nu sunt importante pentru calcularea lungimii listei și este posibil să nu fie calculate deloc. Cu ordine strictă (aplicativă) de evaluare, valorile tuturor argumentelor sunt precalculate înainte ca funcția în sine să fie evaluată. Cu o abordare nestrictă (ordine normală de evaluare), valorile argumentelor nu sunt evaluate până când valoarea lor este necesară atunci când funcția este evaluată.

De regulă, o abordare non-strictă este implementată sub forma reducerii grafice. Evaluarea non-strictă este implicită în mai multe limbi pur funcționale, inclusiv Miranda, Clean și Haskell. [ ]

FP în limbi nefuncționale

În principiu, nu există bariere în scrierea de programe în stil funcțional în limbi care nu sunt considerate în mod tradițional funcționale, la fel cum programele de stil orientate pe obiecte pot fi scrise în limbaje structurale. Unele limbaje imperative acceptă constructe tipice limbajelor funcționale, cum ar fi funcțiile de ordin superior și listele de înțelegere, care facilitează utilizarea stilului funcțional în aceste limbi. Un exemplu ar fi programarea funcțională în Python. Un alt exemplu este limbajul Ruby, care are capacitatea de a crea atât obiecte lambda, cât și capacitatea de a organiza funcții anonime de ordin superior printr-un bloc folosind constructul yield.

Stiluri de programare

Programele imperative tind să sublinieze secvențele de pași pentru a efectua o anumită acțiune, în timp ce programele funcționale tind să sublinieze aranjarea și compoziția funcțiilor, deseori nedesemnând succesiunea exactă a pașilor. Un exemplu simplu de două soluții la aceeași problemă (folosind același limbaj Python) ilustrează acest lucru.

# stil imperativ tinta = # creați o listă goală pentru elementul din lista_sursă: # pentru fiecare element din lista originală trans1 = G(articol) # aplică funcția G(). trans2 = F (trans1) # aplică funcția F().ţintă. adaugă (trans2) # adăugați elementul convertit în listă

Versiunea funcțională arată diferit:

# stil funcțional # Limbile FP au adesea o funcție încorporată compose(). compose2 = lambda A , B : lambda x : A (B (x )) target = map (compose2 (F , G ), listă_sursă )

Spre deosebire de stilul imperativ, care descrie pașii care duc la atingerea unui scop, stilul funcțional descrie relația matematică dintre date și scop.

Mai exact, există patru etape în dezvoltarea stilului funcțional, în ordinea descrescătoare a rolului datelor în programe:

  • Refal (pentru această categorie, reprezentată de o singură limbă, nu există un nume comun);
  • Aplicativ (Lisp, , Tcl, Rebol);
  • Combinatoriu (APL / / , FP/FL);
  • Dotless (concatenativ pur) (Bucurie , Cat , Factor , un subset de PostScript).

În primul caz, întreaga structură a programului este determinată de structura datelor, în ultimul caz, datele ca atare sunt în general absente în cod sursa, acestea sunt doar implicite în intrare. Unele limbi acceptă o serie de stiluri: de exemplu, Haskell vă permite să scrieți atât în ​​stiluri aplicative, combinatorii, cât și fără puncte.

Particularități

Principala caracteristică a programării funcționale, care determină atât avantajele, cât și dezavantajele acestei paradigme, este că implementează model de calcul apatrid. Dacă un program imperativ în orice stadiu de execuție are o stare, adică un set de valori ale tuturor variabilelor și produce efecte secundare, atunci un program pur funcțional nu are stare nici în întregime, nici în părți și nu produce secundare. efecte. Ceea ce se face în limbaje imperative prin atribuirea de valori variabilelor se realizează în limbaje funcționale prin trecerea expresiilor la parametrii funcției. Consecința imediată este că un program pur funcțional nu poate modifica datele pe care le are deja, ci poate genera doar altele noi prin copierea și/sau extinderea celor vechi. Consecința aceleiași este respingerea ciclurilor în favoarea recursiei.

Puncte forte

Îmbunătățirea fiabilității codului

Partea atractivă a calculului fără stat este fiabilitatea crescută a codului datorită structurii clare și absenței necesității de a urmări efectele secundare. Orice funcție funcționează numai cu date locale și funcționează întotdeauna cu ele în același mod, indiferent de unde, cum și în ce circumstanțe este numită. Imposibilitatea mutației datelor atunci când le folosiți în diferite locuri ale programului elimină apariția erorilor greu de detectat (cum ar fi, de exemplu, atribuirea accidentală a unei valori incorecte unei variabile globale într-un program imperativ).

Ușurința de organizare a testării unitare

Deoarece o funcție din programarea funcțională nu poate produce efecte secundare, obiectele nu pot fi modificate atât în ​​interiorul domeniului, cât și în exterior (spre deosebire de programele imperative, unde o funcție poate seta o variabilă externă citită de a doua funcție). Singurul efect al evaluării unei funcții este rezultatul pe care îl returnează, iar singurul factor care afectează rezultatul este valoarea argumentelor.

Astfel, este posibil să testați fiecare funcție dintr-un program prin simpla evaluare a acesteia din diferite seturi de valori ale argumentelor. În acest caz, nu trebuie să vă faceți griji despre apelarea funcțiilor în ordinea corectă sau despre formarea corectă a stării externe. Dacă vreo funcție din program trece testele unitare, atunci puteți fi sigur de calitatea întregului program. În programele imperative, verificarea valorii returnate a unei funcții nu este suficientă: funcția poate modifica starea externă, care trebuie și verificată, ceea ce nu este necesar în programele funcționale.

Opțiuni de optimizare a compilatorului

Caracteristica pozitivă menționată în mod tradițional a programării funcționale este aceea că vă permite să descrieți programul în așa-numita formă „declarativă”, atunci când o succesiune rigidă de efectuare a multor operații necesare calculării rezultatului nu este specificată în mod explicit, ci se formează automat în procesul de calcul al funcțiilor. Această circumstanță, precum și absența stărilor, face posibilă aplicarea unor metode destul de complexe de optimizare automată la programele funcționale.

Capacități de concurență

Un alt avantaj al programelor funcționale este că oferă cele mai largi posibilități de paralelizare automată a calculelor. Deoarece absența efectelor secundare este garantată, în orice apel de funcție este întotdeauna posibil să se evalueze doi parametri diferiți în paralel - ordinea în care sunt evaluați nu poate afecta rezultatul apelului.

Defecte

Dezavantajele programării funcționale provin din aceleași caracteristici. Absența sarcinilor și înlocuirea lor cu generarea de noi date duce la necesitatea alocării constante și eliberării automate a memoriei, prin urmare, în sistemul de execuție al unui program funcțional, un colector de gunoi foarte eficient devine o componentă indispensabilă. Modelul de calcul non-strict duce la o ordine imprevizibilă a apelurilor de funcții, ceea ce creează probleme în I/O, unde ordinea operațiilor este importantă. De asemenea, evident că funcțiile de intrare în forma lor naturală (cum ar fi getchar din biblioteca standard a limbii) nu sunt pure, deoarece pot returna valori diferite pentru aceleași argumente și sunt necesare anumite trucuri pentru a elimina acest lucru.

Pentru a depăși deficiențele programelor funcționale, chiar și primele limbaje de programare funcționale au inclus nu numai instrumente pur funcționale, ci și mecanisme de programare imperativă (atribuirea, bucla, „PROGN implicit” erau deja în Lisp). Folosirea unor astfel de instrumente rezolvă unele probleme practice, dar înseamnă îndepărtarea de ideile (și avantajele) de programare funcțională și scrierea de programe imperative în limbaje funcționale. În limbajele pur funcționale, aceste probleme sunt rezolvate prin alte mijloace, de exemplu, în Haskell, I/O este implementat folosind monade, un concept non-trivial împrumutat din teoria categoriilor.

Mulțimea X se numește domeniul funcției P, iar mulțimea Y ​​este domeniul funcției P. Valoarea x din P(x), care este orice element din mulțimea X, se numește variabilă independentă, iar valoarea valoarea y din mulțimea Y, definită de ecuația y = P(x ) se numește variabilă dependentă. Uneori, dacă funcția f nu este definită pentru tot x din X, se vorbește de o funcție parțial definită, în in caz contrarînseamnă definiția completă.

Foarte proprietate importantă definiție matematică funcția este aceea că, dat un argument x, acesta determină întotdeauna aceeași valoare, deoarece nu are efecte secundare. Efectele secundare în limbajele de programare sunt asociate cu variabile care modelează locațiile de memorie. functie matematica definește o valoare, mai degrabă decât să specifice o secvență de operații asupra numerelor stocate în celulele de memorie pentru a calcula o anumită valoare. Nu există variabile în sensul dat în limbajele de programare imperative, deci nu pot exista efecte secundare.

Într-un limbaj de programare, bazat pe conceptul matematic al unei funcții, pot fi reprezentate programe, proceduri și funcții. În cazul unui program, x este intrarea și y este ieșirea. În cazul unei proceduri sau funcție, x caracterizează parametrii, iar y caracterizează valorile returnate. În orice caz, x poate fi denumit „intrări” și y ca „ieșiri”. Prin urmare, într-o abordare funcțională a programării, nu se face nicio distincție între un program, o procedură și o funcție. Pe de altă parte, cantitățile de intrare și de ieșire sunt întotdeauna diferite.

În limbajele de programare, definiția unei funcții și aplicarea unei funcții sunt destul de clar separate. O definiție a funcției descrie modul în care o valoare poate fi calculată pe baza parametrilor formali. Utilizarea unei funcții înseamnă apelarea unei anumite funcții folosind parametrii actuali. Rețineți că în matematică diferența dintre un parametru și o variabilă nu este întotdeauna evidentă. Foarte des termenul „parametru” este înlocuit cu termenul „variabilă independentă”. De exemplu, în matematică, puteți scrie definiția funcției de pătrat: pătrat (x) \u003d x * x

Să presupunem că pătratul(x) + 2 este făcut pentru x. . .

Principala diferență dintre programarea imperativă și cea funcțională este interpretarea conceptului de variabilă. În matematică, variabilele sunt reprezentate ca valori reale, în timp ce în limbajele de programare imperative, variabilele se referă la zonele de memorie în care sunt stocate valorile lor. Atribuțiile vă permit să schimbați valorile în aceste zone de memorie. Dimpotrivă, în matematică nu există concepte de „zonă de memorie” și „atribuire”, deci un operator precum x = x + 1

nu are sens. Programarea funcțională se bazează pe o abordare matematică a conceptului de „variabilă”. În programarea funcțională, variabilele sunt asociate cu valori, dar nu au nimic de-a face cu zonele de memorie. După legarea unei variabile la o valoare, valoarea variabilei se modifică

Vorbim despre principiile programării funcționale: care sunt dezavantajele sale și ce limbaje sunt funcționale.

Noțiuni de bază

Programarea funcțională se bazează pe câteva concepte importante: absența efectelor secundare și a datelor mutabile, funcții pure și compoziția lor. Să ne uităm la fiecare dintre ele.

Funcții pure

O funcție pură este cât se poate de simplă. Ar trebui să returneze întotdeauna același rezultat. Uită-te la această funcție JavaScript:

varz = 10; funcția adaugă (x, y) ( return x + y; )

varz = 10;

funcția adună (x, y) (

returnează x + y

Desigur, acesta este un exemplu artificial, dar demonstrează foarte bine cum ar trebui să funcționeze o funcție pură. Pentru aceleasi valori Xși y vom obține întotdeauna același rezultat al funcției. Previzibilitatea este o parte importantă a funcționării programului în programarea funcțională.

Date modificabile și efecte secundare

Să revenim la exemplul de cod. Dacă adăugăm ca argument funcție adăuga(), variabil z, care este declarat mai sus, funcția noastră nu va mai fi curată și previzibilă. De ce? deoarece z declarată ca variabilă normală: poate fi schimbată de oriunde în program.

Ideea din spatele imuabilității în programarea funcțională este că variabilele nu pot fi modificate odată declarate.

Cod corect pentru o funcție pură cu z ar trebui sa arate asa:

const x = 10; const z = 10; adaugă (x, z); // returnează 20

const x = 10 ;

const z = 10 ;

adaugă (x, z); // returnează 20

În acest caz, funcția va returna întotdeauna un rezultat previzibil. Dacă o funcție nu funcționează în mod previzibil, va duce la efecte secundare nedorite.

Un alt exemplu de cod nefuncțional sunt buclele clasice. Să ne amintim ce tipic pentru buclăîn JavaScript:

var acc = 0; pentru (var i = 1; i<= 10; ++i) { acc += i; } console.log(acc); // выведет 55

var acc = 0 ;

pentru (var i = 1; i<= 10 ; ++ i ) {

acc += i ;

consolă. log(acc) ; // imprimă 55

Primul lucru de observat este că bucla folosește var i a număra pașii. În programarea funcțională, utilizarea unei astfel de bucle este inacceptabilă, deoarece poate duce la un comportament imprevizibil al buclei.

Pentru a evita efectele secundare, FP folosește funcții recursive pentru a crea bucle.

function sumRange(start, end, acc) ( if (start > end) ( return acc; ) else ( return sumRange(start + 1, end, acc + start); ) ) console.log(sumRange(1, 10, 0) )); // imprimă 55

funcția sumRange (start , end , acc ) (

dacă (început > sfârșit) (

return acc ;

) altfel (

return sumRange (start + 1 , end , acc + start ) ;

consolă. log (sumRange (1 , 10 , 0 ) ) ; // imprimă 55

Acest design vă permite să utilizați constante pentru a defini începutul, sfârșitul buclei și pasul. Acest tip de buclă se bazează pe ideea de a apela o funcție în sine, sau un apel recursiv. În exemplul de mai sus, funcția sumRange() cu argumentele date, verifică condiția, iar în cazul unui rezultat fals, se autoinvocă cu argumentele modificate.

Compoziția funcției

Compoziția de funcții este o abordare în programarea funcțională care implică apelarea unor funcții ca argumente pentru altele pentru a crea funcții compozite complexe din altele mai simple.

function addOne(x) ( return x + 1; ) function timesTwo(x) ( return x * 2; ) console.log(addOne(timesTwo(3))); // imprimă 7 console.log(timesTwo(addOne(3))); // iese 8

funcția addOne(x)(

returnează x + 1 ;

funcția timesTwo(x)(

În exemplul de mai sus, am descris două funcții simple: adauga unu(adaugă una la argument) și ori Doi(înmulțește argumentul cu doi). Tehnica de layout ne permite să apelăm aceste două funcții una în cealaltă într-o ordine diferită. Ca rezultat, cu ordine logică diferită a apelării funcțiilor pure și aceeași valoare argument, avem o funcționalitate mai complexă care ne oferă rezultatul dorit și îl face previzibil.

Beneficiile programării funcționale

Programarea funcțională face codul mai curat, mai previzibil și mai ușor de citit. Utilizarea principiilor FP ajută la eliminarea abstracțiilor inutile cu comportament imprevizibil, prin urmare, pentru a face programul mai previzibil și a reduce numărul de erori posibile.

Dezavantajele programării funcționale

Programarea funcțională nu este potrivită pentru algoritmii bazați pe grafice, din cauza relativ mai multor muncă lentă programe. OP este, în general, slab aplicabil deciziilor care au fost bazate pe o abordare imperativă de ani de zile.

Cel mai important, limbile funcționale nu au un dicționar de set neordonat eficient. În comparație cu tabelele hash, dicționarele funcționale funcționează semnificativ mai rău, iar acest lucru poate fi critic pentru unele aplicații. Cu toate acestea, aceste neajunsuri pot fi atribuite doar limbilor imperative.

Programare funcțională în limbaje

Deoarece programarea funcțională este în primul rând o abordare a scrierii codului, puteți utiliza principiile sale în orice limbaj. Cu toate acestea, există limbi special adaptate pentru abordarea funcțională. Primul și cel mai faimos dintre acestea este Lisp. A apărut în 1958. Autorul său este John McCarthy, un informatician și autor al termenului „ inteligenţă artificială". Lisp este încă popular printre designerii AI astăzi.

Limbaje funcționale mai moderne, cum ar fi Elm și Elixir, câștigă încet și constant popularitate conform GitHub și Stack Overflow. Creșterea popularității JavaScript a dus, de asemenea, la un interes sporit pentru conceptele de programare funcțională pentru aplicare în limbaj.

  • Serghei Savenkov

    un fel de recenzie „rare”... parcă s-ar grăbi undeva