Programare funcțională în Python

Deși utilizatorii se gândesc de obicei la Python ca pe un limbaj procedural și orientat pe obiecte, acesta conține tot ceea ce este necesar pentru a sprijini o abordare complet funcțională a programării.






Acest articol discută conceptele generale ale programării funcționale și ilustrează modalitățile de implementare a unei abordări funcționale a Python.

Ce este Python?


Python este un limbaj interpretat gratuit, foarte înalt, dezvoltat de Guido van Rossum. Combină sintaxa transparentă cu semantica puternică (dar opțională) orientată spre obiect. Python este disponibil pe aproape toate platformele existente și are o portabilitate foarte mare între platforme.

Ce este programarea funcțională?


Cel mai bine este să se înceapă cu o întrebare dificilă - dar ce, exact, este o „programare funcțională (FP)“? Un posibil răspuns este - „este când scrieți într-o limbă ca Lisp, Scheme, Haskell, ML, Ocaml, Clean, Mercur sau Erlang (sau chiar unele altele).“ Acest răspuns, desigur, adevărat, dar nu de mult clarifică esența. Din păcate, pentru a obține o imagine clară a ceea ce este FP, este foarte dificil, chiar și în rândul lor programatori funcționale. Îmi amintesc parabola celor trei bărbați orbi și elefantul. De asemenea, este posibil să se determine FP, contrastând lui „programare imperativă“ (ce faci în limbi, cum ar fi C, Pascal, C ++, Java, Perl, Awk, TCL, și multe altele - cel puțin pentru cea mai mare parte).

Funcțiile sunt obiecte din clasa întâi. Ie tot ce se poate face cu "date" se poate face cu funcții (cum ar fi trecerea unei funcții a unei alte funcții ca parametru).
* Folosiți recursivitatea ca principală structură de control a debitului de control. În unele limbi, nu există altă structură de buclă decât recursivitatea.
* Concentrați-vă pe procesarea listelor (listele, prin urmare numele Lisp - prelucrarea LISt). Listele cu traversarea recursivă a sublistelor sunt adesea folosite ca înlocuitori pentru bucle.
* Limbile funcționale "pure" evită efectele secundare. Aceasta elimină abordarea aproape omniprezentă în limbile imperative, în care aceeași variabilă este atribuită succesiv unor valori diferite pentru a urmări starea programului.
* FP nu aprobă sau interzice complet declarațiile, folosind în schimb calcularea expresiilor (adică funcțiile cu argumente). În cazul extrem, un program este o expresie (plus definiții suplimentare).
* FP se concentrează pe ceea ce ar trebui calculat, nu cum.
* Cele mai multe funcții FP utilizează funcții de "comandă înaltă" (funcții care funcționează funcții, funcții de operare).

Apărătorii programării funcționale demonstrează că toate aceste caracteristici conduc la o dezvoltare mai rapidă a unui cod mai scurt și fără erori. În plus, teoreticienii de la științele informatice, logică și matematică consideră că procesul de dovedire a proprietăților formale pentru limbile și programele funcționale este mult mai simplu decât pentru cele imperative.

Funcționalitate inerentă Python


Python suportă majoritatea funcțiilor de programare funcțională, începând cu versiunea Python 1.0. Dar, ca cele mai multe caracteristici Python, ele sunt prezente într-un limbaj foarte mixt. Ca și în cazul capabilităților Python orientate pe obiect, puteți folosi ceea ce aveți nevoie și puteți ignora orice altceva (până când aveți nevoie de el). În Python 2.0, a fost adăugată o "decorație sintactică" foarte reușită - se construiește listă (comprehensions list). Deși nu adaugă caracteristici fundamentale noi, construcțiile de listă fac folosirea multor caracteristici vechi mult mai plăcute.

Elementele de bază ale FP în Python - harta () funcție, reduce (), filtru () și lambda operatorului. În Python 1.x introdus aplica, de asemenea funcția (), o funcție convenabilă pentru aplicarea directă pe lista returnată de către cealaltă. Python 2.0 oferă o sintaxă îmbunătățită pentru acest lucru. Oarecum neașteptat, dar aceste caracteristici și doar câțiva operatori de bază aproape suficient pentru a scrie orice program în Python; în special, toate declarațiile de control ( „dacă“, „Elif“, „altceva“, „afirma“, „încercați“, „cu excepția“, „în cele din urmă“, „pentru“, „pauză“, „continuă“, „în timp ce“ , „def“) poate fi reprezentat într-un stil funcțional, folosind doar funcțiile și operatori. În ciuda faptului că problema reală este eliminarea tuturor comenzilor de control al debitului poate fi numai util pentru prezentarea la concursul „Python ininteligibilă“ (cu cod care arata ca programul de Lisp), este în valoare de înțelegerea modului în care FP exprimă structuri de gestionare prin intermediul apelurilor de funcții și recursivitate.

Excluderea comenzilor de control al fluxului


Primul lucru pe care merită să ne amintim în acest exercițiu - că Python „scurt-circuit“ vyrazheniy.1 logica de calcul se dovedește că aceasta oferă o unitate echivalentă „în cazul în care“ / „Elif“ / „altceva“ sub formă de exprimare. Deci:

# ------ "Short-circuit" apeluri condiționale în Python ----- #
# Structuri de control convenționale
dacă : func1 ()
Elif : func2 ()
altfel: func3 ()

# Expresie echivalentă "cu scurtcircuit"
( și func1 ()) sau ( și func2 ()) sau (func3 ())

# Exemplu de expresie "scurt-circuitată"
>>> x = 3
>>> def pr (s): întoarcere s
>>> (x == 1 și pr ('one')) sau (x == 2 și pr ('two')) sau (pr ('
„Altele“
>>> x = 2
>>> (x == 1 și pr ('one')) sau (x == 2 și pr ('two')) sau (pr ('
„Doi“


Se pare că versiunea noastră de apeluri condiționate cu ajutorul expresiilor nu este altceva decât un foc salon; Totuși, totul devine mult mai interesant atunci când considerați că operatorul lambda poate conține numai expresii! Odată ce, așa cum tocmai am arătat, expresiile pot conține blocuri condiționate, folosind un scurtcircuit, expresia lambda permite într-o formă generală să reprezinte valorile returnate condiționate. Pe baza exemplului anterior:

# --------- Lambda cu expresii condiționate scurte în Python ------- #
>>> pr = lambda s. s
>>> namenum = lambda x. (x == 1 și pr ("unul"))
. sau (x == 2 și pr ("doi"))
. sau (pr ("alta"))
>>> namenum (1)
'One'
>>> namenum (2)
„Doi“
>>> namenum (3)
„Altele“

Funcționează ca obiecte de primă clasă


Aceste exemple au asistat deja, deși nu este evident, statutul de funcții, cum ar fi obiecte de primă clasă în Python. Faptul este că, prin crearea unui operator de lambda obiect funcție, am făcut o acțiune foarte frecvente. Am fost capabili de a lega obiectul nostru la numele și namenum pr exact în același mod ca acestea ar putea lega numărul 23 sau linia de „spam“ pentru aceste nume. Dar, la fel ca și numărul 23 poate fi utilizat fără a adera la nici un nume (de exemplu, ca un argument la funcția), putem folosi obiectul funcției create de lambda, care nu aderă la nici un nume. Funcția în Python - doar o altă valoare cu care se poate face ceva.







Principalul lucru pe care îl facem cu obiectele noastre de primă clasă este transferul lor către funcțiile încorporate (), reduce () și filtru (). Fiecare dintre aceste funcții prelucrează obiectul funcției ca primul argument. map () aplică funcția trecută la fiecare element din lista (liste) transferate și returnează o listă de rezultate. reduce () aplică funcția trecută la fiecare valoare din listă și la acumulatorul intern de rezultate; de exemplu, reduce (lambda n, m: n * m, intervalul (1.10)) înseamnă 10! (factorial 10 - înmulțiți fiecare element cu rezultatul multiplicării anterioare). filter () aplică funcția trecută la fiecare element al listei și returnează o listă a acelor elemente din lista sursă pentru care funcția trecută a returnat valoarea adevărului. De asemenea, transferăm adesea obiectele funcționale la propriile funcții, dar mai des și unele combinații ale funcțiilor încorporate mai sus menționate.

Prin combinarea acestor trei funcții FP încorporate, puteți implementa o gamă neașteptat de largă de operațiuni de flux de control fără a recurge la declarații, ci doar la expresii.

Cicluri funcționale în Python


Înlocuirea buclelor pentru expresii este la fel de simplă ca înlocuirea blocurilor condiționale. "pentru" poate fi tradus direct în hartă (). Ca și în cazul executării condiționate, trebuie să simplificăm blocul de afirmații la apelul unei funcții (suntem aproape de a învăța cum să facem acest lucru în cazul general):

# ---------- Buclele funcției "pentru" în Python ---------- #
pentru e în lst. func (e) # buclă pe instrucțiunea "pentru"
map (func. lst) # buclă bazată pe hartă ()


Apropo, o tehnică similară este folosită pentru a implementa o execuție consistentă a programului, folosind o abordare funcțională. Ie programarea imperioasă, în cea mai mare parte, constă în declarații care necesită "să faci asta, apoi să faci ceva, apoi să faci altceva". 'map ()' permite ca aceasta să fie exprimată ca:

# ----- Execuție secvențială funcțională în Python ---------- #
# creați o funcție de apelare a funcțiilor auxiliare
do_it = lambda f. f ()

# Fie f1, f2, f3 (etc) funcții care efectuează acțiuni utile
map (do_it. [f1.f2.f3]) # execuție secvențială implementată pe hartă ()


În general, întregul program principal poate fi un apel la 'map ()' cu o listă de funcții care trebuie să fie numită secvențial pentru a executa programul. O altă proprietate convenabilă a funcțiilor ca obiecte este aceea că le puteți pune în listă.

Pentru a traduce "în timp" direct este un pic mai complicat, dar este destul de posibil:

# -------- Bucla funcțională 'în timp' în Python ---------- #
# Normal (pe baza instrucțiunii "în timp")
în timp ce :


dacă :
pauză
altceva:

# O buclă recursivă într-un stil funcțional
def while_block ():


dacă :
retur 1
altceva:

retur 0

în timp ce_FP = lambda. ( și while_block ()) sau while_FP ()
while_FP ()


Opțiunea noastră "în timp" necesită încă o funcție while_block (), care poate conține nu numai expresii, ci și declarații. Dar am putea continua să excludem în continuare declarațiile din această funcție (cum ar fi, de exemplu, înlocuirea blocului "if / else" în șablonul de mai sus cu o expresie scurt-circuitată). În plus, o inspecție de rutină la fața locului (cum ar fi "în timp ce myvar == 7") este puțin probabil să fie utilă, deoarece corpul buclă (în forma prezentată) nu poate schimba nici o variabilă (deși variabilele globale pot fi modificate în while_block ()). O modalitate de a aplica o condiție mai utilă este de a forța while_block () să returneze o valoare mai semnificativă și să o compare cu condiția de terminare. Merită să examinăm un exemplu real de excludere a declarațiilor:

# ---------- Bucla funcțională "ecou" în Python ------------ #
# Versiunea imperativă a "ecou ()"
def echo_IMP ():
în timp ce 1:
x = raw_input ("IMP -")
dacă x == 'quit':
pauză
altfel
print x
echo_IMP ()

# O funcție de serviciu care implementează "identitatea cu un efect secundar"
def monadic_print (x):
print x
retur x

Versiunea # FP a "ecou ()"
echo_FP = lambda. monadic_print (raw_input ("FP -")) == 'quit' sau echo_FP ()
echo_FP ()


Am realizat că am exprimat un mic program care include intrarea / ieșirea, buclele și condițiile sub forma unei expresii pure cu recursivitate (de fapt - sub forma unui obiect funcțional, care, dacă este necesar, poate fi transferat oriunde). Încă mai folosim funcția de serviciu monadic_print (), dar această funcție este complet generală și poate fi utilizată în orice expresie funcțională. pe care o vom crea mai târziu (acesta este un cost unic) .2 3 Observați că orice expresie care conține monadic_print (x) este evaluată în același mod ca și cum ar conține doar x. În FP (în particular, în Haskell) există un concept de "monadă" pentru o funcție care "nu face nimic și provoacă un efect secundar atunci când se realizează".

Eliminarea efectelor secundare


După toate lucrările făcute pentru a scăpa de construcții complet semnificative și pentru a le înlocui cu expresii neinteligibile imbricate, apare o întrebare naturală: "De ce?!". Revedind descrierile mele despre caracteristicile FP, putem vedea că toate acestea sunt realizate în Python. Dar cea mai importantă (și, cel mai probabil, cea mai frecvent utilizată) caracteristică este eliminarea efectelor secundare sau, cel puțin, limitarea utilizării lor de către zone speciale, cum ar fi monadele. Un procent mare de erori de program și principala problemă care necesită utilizarea programelor de depanare se întâmplă deoarece variabilele primesc valori incorecte în timpul executării programului. Programarea funcțională ocolește această problemă, pur și simplu prin faptul că nu atribui deloc valori variabilelor.

Să ne uităm la o zonă cu totul comună de cod imperativ. Scopul său este de a imprima o listă de perechi de numere al căror produs este mai mare de 25. Numerele care formează perechile sunt ele însele luate din celelalte două liste. Toate acestea sunt foarte asemănătoare cu ceea ce fac programatorii în multe domenii ale programelor lor. O abordare imperativă a acestei probleme ar putea arăta astfel:

# --- cod imperativ pentru "lucrări de tipărire" ---- #
# Stil procedural - căutarea unor lucrări mari folosind bucle imbricate
xs = (1. 2. 3. 4)
ys = (10. 15. 3. 22)
bigmuls = []
#. alt cod.
pentru x în xs:
pentru y în ys:
#. alt cod.
dacă x * y> 25:
bigmuls. adăugați ((x. y))
#. alt cod.
#. alt cod.
tipăriți bigmuls

O abordare funcțională a sarcinii noastre elimină complet erorile asociate cu efectele secundare. O posibilă soluție ar putea fi aceasta:

# --- Cod funcțional pentru căutarea / tipărirea lucrărilor mari în Python ---- #
bigmuls = lambda xs. YS. filtru (lambda (x. y): x * y> 25. combinați (xs. ys))
combine = lambda xs. YS. hartă (fără .xs * len (ys), dupelms (ys. len (xs)))
dupelms = lambda lst. n. reduce (lambda s. t + h harta (lambda l. n = n. [l] * n. lst))
imprimarea bigmuls ((1. 2. 3. 4), (10. 15. 3. 22))


Asociază în exemplu un anonim ("lambda") cu nume, dar acest lucru nu este necesar. În schimb, am putea pur și simplu să cuprindem definiții. Am folosit nume atât pentru o mai mare lizibilitate, cât și pentru că combine () este, în orice caz, o funcție de serviciu excelentă (generează o listă a tuturor perechilor posibile de articole din două liste). La rândul său, dupelms () este în principiu doar o parte auxiliară a combine (). Deși acest exemplu funcțional este mai verbitabil decât imperativ, dacă reutilizați funcțiile de serviciu, codul din bigmuls () real va fi probabil mai concis decât în ​​versiunea imperativă.

Avantajul real al acestui exemplu funcțional este că în el absolut nici o variabilă nu își schimbă sensul. Orice efect secundar neașteptat asupra codului ulterior (sau din partea codului precedent) este pur și simplu imposibil. Desigur, prin ea însăși, absența efectelor secundare nu garantează codul fără erori, dar în orice caz, acesta este un avantaj. Cu toate acestea, rețineți că Python, spre deosebire de multe limbi funcționale, nu împiedică re-legarea numelor bigmuls, combine și dupelms. Dacă combinați combinația () începe să însemne altceva - din păcate! Ar fi posibil să se dezvolte o clasă Singleton care să susțină o singură legare de acest tip (de exemplu, "s.bigmuls", etc.), însă acest lucru depășește domeniul de aplicare al acestui articol.

Un alt lucru de observat este că sarcina pe care tocmai am rezolvat-o este adaptată exact la noile caracteristici ale Python 2.0. În loc de exemplele de mai sus - imperativ sau funcțional - cea mai bună (și funcțională) tehnică este după cum urmează:

# ----- Codul Python pentru "bigmuls" folosind lista construiri (comprehensions lista) ----- #
(x, y) pentru x în (1. 2. 3. 4) pentru y în (10. 15. 3. 22) dacă x * y> 25]

concluzie







Articole similare

Trimiteți-le prietenilor: