Sunteți pe pagina 1din 14

Programare funct, ională folosind Haskell

S, tefan Ciobâcă

March 13, 2019


2
Chapter 1

Introducere ı̂n Haskell

“SQL, Lisp, and Haskell are the only


programming languages that I’ve seen where
one spends more time thinking than typing.”
(Philip Greenspun)

În laboratorul aferent cursului “Programare Funct, ională”, vom studia limbajul de
programare Haskell. Limbajul Haskell este un limbaj de programare general, pur funct, ional,
lenes, s, i tipizat static. În cursul acestui laborator, vom studia s, i vom ı̂nt, elege fiecare din-
tre aceste particularităt, i ale limbajului Haskell.

Cultură generală
Limbajul Haskell poartă numele logicianului Haskell Brooks Curry. Curry
a lucrat ı̂n special ı̂n domeniul logicii combinatoriale, dar a avut contribut, ii
importante s, i ı̂n alte domenii ale informaticii. O anecdotă interesantă este că
fiecare dintre cele trei nume ale lui Haskell Brooks Curry a dat numele câte
unui limbaj de programare.

Limbajul Haskell a luat nas, tere ı̂n anul 1987, când la conferint, a FPCA din Portland,
Oregon, participant, ii au hotărât să dezvolte un standard deschis pentru un limbaj de
programare ne-strict (vom vedea ce ı̂nseamnă ne-strict ı̂n continuare). Astfel de limbaje
de programare existau deja, dar nu erau open-source. Cel mai cunoscut era limbajul
Miranda, care a inspirat mult dezvoltarea Haskell.
Specificat, ia limbajului Haskell este definită de un comitet de standardizare s, i ex-
istă mai multe versiuni, dintre care cele mai recente sunt Haskell 98, definit ı̂n “Haskell
98 Language and Libraries: The Revised Report” s, i, respectiv, Haskell 2010, definit ı̂n
“Haskell 2010 Language Report”. Există mai multe implementări ale limbajului Haskell.
Cea mai cunoscută implementare a limbajului Haskell este GHC (Glasgow Haskell Com-
piler), un compilator open-source al cărui dezvoltator principal, Simon Peyton Jones,
lucrează la Microsoft Research ı̂n Cambridge, UK.

3
4 CHAPTER 1. INTRODUCERE ÎN HASKELL

1.1 De ce să studiez limbajul Haskell?


“A language that doesn’t affect
the way you think about programming,
is not worth knowing.”
(Alan Perlis)
Limbajul Haskell este un limbaj de programare general (general-purpose programming
language), ceea ce ı̂nseamnă că poate fi folosit, ı̂n principiu, pentru scrierea oricărui
program imaginabil. Spre deosebite de limbajele generale, limbajele specifice sunt folosite
ı̂ntr-un domeniu restrâns. Un exemplu de limbaj specific pe care probabil ı̂l s, tit, i este
SQL, folosit doar pentru interogarea bazelor de date.
Limbajul de programare Haskell este un limbaj pur funct, ional (purely functional
programming language). Ce ı̂nseamnă pur funct, ional? În mod tradit, ional, limbajele
de programare sunt imperative, ceea ce ı̂nseamnă că programul constă dintr-o secvent, ă
de instruct, iuni (de exemplu, atribuiri) pe care calculatorul le execută, ı̂n principiu, ı̂n
ordine, iar instruct, iunile de control (“if”) sau de repetit, ie (“while”, “for”) pot fi folosite
pentru a modifica fluxul obis, nuit de execut, ie. Exemple de limbaje imperative: C, Pascal,
BASIC.
Spre deosebire de limbajele imperative, unde calculul este realizat prin execut, ia unor
instruct, iuni, ı̂n limbajele funct, ionale calculul este realizat doar prin evaluarea unor expre-
sii s, i prin definirea de funct, ii. În unele limbaje de programare funct, ionale, sunt prezente
anumite trăsături imperative (de exemplu, bucle “while”). În limbajele de programare
pur funct, ionale, orice trăsătură imperativă este interzisă (e.g. nu există instruct, iunea
de atribuire, nu există bucle).
Deoarece instruct, iunea de atribuire este interzisă ı̂n limbajele pur funct, ionale, niciun
program pur funct, ional nu poate produce efecte secundare (un efect secundar fiind de
exemplu modificarea valorii unei variabile globale). Vom vedea că lipsa buclelor (“while”,
“for”, etc.) s, i a instruct, iunii de atribuire nu limitează puterea de calcul a limbajului s, i
că orice program care poate fi scris, de exemplu, ı̂n limbajul C poate fi scris s, i in Haskell,
codificând buclele sub forma unor funct, ii recursive.
Din acest motiv, programarea funct, ională este o paradigmă de programare care face
parte din paradigmele de programare declarativă, deoarece un program funct, ional nu
descrie “cum” se realizează un calcul, ci doar “care” ar trebui să fie rezultatul calculului.

Cultură generală
Un alt exemplu de paradigmă de programare declarativă este programarea
logică, unde programele sunt o secvent, ă de clauze Horn de ordinul I, iar
execut, ia este realizată printr-o formă de rezolut, ie.

Avantajul major al limbajelor pur funct, ionale este că lipsa efectelor secundare sim-
plifică semnificativ rat, ionamentele despre comportamentul unui program. Mai mult,
1.1. DE CE SĂ STUDIEZ LIMBAJUL HASKELL? 5

limbajele pur funct, ionale ı̂ncurajează programatorul să scrie cod despre care se poate
rat, iona mai simplu. Din acest motiv, este mult mai us, or să ne convingem că un program
pur funct, ional este corect.
Altă particularitate importantă a limbajului Haskell este evaluarea lenes, ă. Evaluarea
lenes, ă asigură faptul că o expresie este evaluată doar dacă acest lucru este strict necesar.
De exemplu, dacă o funct, ie foloses, te valoarea unui parametru doar ı̂n anumite cazuri,
atunci parametrul nu este evaluat decât ı̂n acele cazuri. Evaluarea lenes, ă poate eficientiza
execut, ia unui program, dar, mai important, permite abordări mai deseobite, cum ar fi
folosirea structurile de date infinite.
Haskell este un limbaj tipizat static. Acest lucru ı̂nseamnă că orice expresie dintr-
un program Haskell are asociat un tip ı̂ncă din momentul compilării. Compilatorul se
asigură că programul este bine tipizat, generând altfel o eroare de compilare. Acest
lucru ı̂nseamnă că un program nu poate produce erori de tip, cum ar fi ı̂nmult, irea
unui număr ı̂ntreg cu un s, ir de caractere, la rulare. Limbajele de programare tipizate
dinamic, cum ar fi Python, nu detectează erorile de tipuri decât ı̂n momentul rulării
programului. Dezavantajul limbajelor tipizate dinamic este că erorile de tip care apar
doar ı̂n cazuri rare nu sunt ı̂n general detectate ı̂n timpul dezvoltării/testării programului,
ci sunt detectate direct de către utilizatori.
Sistemul de tipuri din Haskell este extrem de expresiv, mult mai expresiv decât,
de exemplu, sistemul de tipuri al limbajului C. Cu toate acestea, nu este necesar ca
toate funct, iile/variabilele să fie adnotate explicit cu tipul lor, ca ı̂n C. Haskell cont, ine
un motor de inferent, ă de tipuri care poate, ı̂n cele mai multe cazuri, să calculeze tipul
funct, iilor/variabilelor din modul ı̂n care sunt folosite. De exemplu, dacă o variabilă de
program x este comparată cu un caracter, atunci tipul lui x trebuie să fie Char.
Limbajul Haskell este folosit ı̂n special ı̂n mediul universitar pentru dezvoltarea de
unelte software, ı̂n general prototip, care implementează rezultate ale cercetării (algo-
ritmi) de ultimă oră, dar s, i in industrie. Este put, in probabil să găsit, i un job unde să
programat, i ı̂n Haskell, dar nu imposibil.

Cultură generală
Sloganul neoficial al limbajului Haskell este “avoid success at all costs”. În mod
aparent paradoxal, dezvoltatorii limbajului nu ı̂s, i doresc ca acesta să devină
un succes comercial. Folosirea pe scară largă a limbajului Haskell ar limita
evolut, ia acestuia, deoarece orice schimbare majoră ar avea impact asupra unui
număr prea mare de persoane.

Totus, i, multe dintre conceptele pe care le vet, i studia vă vor fi de folos chiar dacă vet, i
programa ı̂ntr-un limbaj obis, nuit, cum ar fi C++, Java sau C#. Cu trecerea timpului,
aceste limbaje evoluează s, i se apropie din ce ı̂n ce mai mult de limbajele funct, ionale.
De exemplu, lambda-expresiile adăugate ı̂n C++11 sunt inspirate direct din limbajele
funct, ionale.
Mai mult, studiul limbajului Haskell vă va schimba profund modul de gândire s, i
6 CHAPTER 1. INTRODUCERE ÎN HASKELL

vă va transforma ı̂n programatori mai buni. De asemenea, fiind deosebit de expresiv
(programele Haskell sunt de obicei mai scurte decât programele C echivalente), vet, i
putea folosi Haskell pentru a crea rapid prototipuri ale programelor pe care dorit, i să le
dezvoltat, i.

1.2 Cum instalez Haskell pe calculatorul meu?


“Everything should be built top-down,
except the first time.”
(Alan Perlis)
Cea mai bună implementare a limbajului Haskell este Glasgow Haskell Compiler
(GHC). Totus, i, puterea limbajului Haskell stă s, i ı̂n numărul impresionant de biblioteci
software care au fost dezvoltate s, i care pot fi folosite ı̂n Haskell.
“Haskell Platform” este o distribut, ie a limbajului Haskell care combină compilatorul
GHC cu cele mai importante biblioteci Haskell existente. Haskell Platform este us, or de
instalat s, i este disponibil pentru toate cele trei sisteme de operare cele mai folosite la
momentul actual: Windows, Mac OS X s, i Linux.
Vă recomand să descărcat, i ultima versiune a Haskell Platform pentru sistemul de
operare pe care ı̂l folosit, i de la adresa http://www.haskell.org/platform/ s, i să o
instalat, i, urmând instruct, iunile de instalare de pe site.
Pe calculatoarele din laboratoarele Facultăt, ii de Informatică ar trebui ca Haskell
Platform să fie deja instalată.
Putet, i verifica dacă at, i instalat cu succes Haskell Platform din linia de comandă a
sistemului de operare pe care ı̂l folosit, i rulând comanda:

ghci

Dacă instalarea a decurs cu succes, vet, i obt, ine un rezultat similar cu cel din Figura 1.1.
Comanda ghci pornes, te compilatorul GHC ı̂n modul interactiv. În acest mod de lu-
cru, GHC cites, te o expresie Haskell, o evaluează, afis, ează rezultatul evaluării s, i reia din
nou acest proces. Acest tip de interact, iune este cunoscut s, i sub numele de toplevel (ı̂n
cazul limbajului OCaml), sau REPL (read-evaluate-print loop) ı̂n cazul limbajelor Com-
mon Lisp, Standard ML, Scheme, Python s, i altele. Un mod similar de lucru este disponi-
bil s, i pentru limbajul JavaScript (consola JavaScript), dacă accesăm ı̂ntr-un browser
modern modul dezvoltator.

Cultură generală
Modul interactiv permite experimentarea rapidă a unor idei s, i favorizează o
manieră de dezvoltare a programelor de tip bottom-up (ı̂n contrast cu dez-
voltarea de tip top-down). Limbajele tradit, ionale cum ar fi C(++), Java sau
C# nu dispun, ı̂n principiu, de un astfel de mod interactiv de lucru.
1.2. CUM INSTALEZ HASKELL PE CALCULATORUL MEU? 7

Figure 1.1: Rularea ghci.

După cum am discutat deja, ı̂n modul interactiv, GHC cites, te o expresie, o evaluează,
afis, ează rezultatul evaluării s, i apoi reı̂ncepe. Cele mai simple expresii Haskell sunt cele
care cont, in numere ı̂ntregi s, i operat, ii matematice obis, nuite peste numere ı̂ntregi. De
exemplu, tastat, i

3 + 4 * 5

s, i apoi apăsat, i tasta Enter pentru a calcula rezultatul expresiei matematice 3 + 4 × 5.


Pentru a calcula 2100 , evaluat, i folosind ghci expresia

2^
100
8 CHAPTER 1. INTRODUCERE ÎN HASKELL

Figure 1.2: Rezultatul evaluării a două expresii Haskell.

s, i vet, i obt, ine rezultatul prezentat ı̂n Figura 1.2.


Pentru a părăsi ghci, ı̂n loc de a introduce o expresie Haskell, este suficient să
introducet, i comanda :quit (atent, ie la :) sau forma sa scurtă, :q, as, a cum este demon-
strat ı̂n Figura 1.3.
În afara expresiilor Haskell, ghci poate executa comenzi. Comenzile ı̂ncep ı̂ntotdeuna
cu caracterul : s, i ele nu sunt expresii Haskell. Cele mai multe comenzi pot fi abreviate.
De exemplu, am văzut deja comanda :quit (cu varianta scurtă :q), care părăses, te
mediul interactiv al GHC. O altă comandă utilă este :help (cu varianta scurtă :h).
Această comandă afis, ează toate comenzile disponibile.

1.3 Expresii simple ı̂n Haskell


După cum am explicat deja, ı̂n Haskell calculul nu se face prin executarea unei secvent, e
de instruct, iuni, ci doar prin evaluarea unor expresii. Cele mai simple expresii sunt
cele care cont, in numere s, i operatorii matematici standard. Mai jos este un exemplu de
1.3. EXPRESII SIMPLE ÎN HASKELL 9

Figure 1.3: Cum se părăses, te modul interactiv al GHC.

interact, iune cu ghci ı̂n care se evaluează câteva expresii.


Important! În cele ce urmează, de fiecare dată când vom prezenta o interact, iune
cu ghci, textul care trebuie introdus de la tastatură va fi precedat de caracterul >, iar
răspunsul sistemului va fi prezentat pe linia sau pe liniile ce urmează.

> 3 + 4 * 5
23
> 3 - 4
-1
> (3 + 4) * 5
35
> 3 / 4
0.75
> -3
-3
> 4 + (-3)
10 CHAPTER 1. INTRODUCERE ÎN HASKELL

1
> 2 ** 5
32.0
> 2 ^ 5
32
> 2 ^ 100
1267650600228229401496703205376
> 2.25 ** 3
11.390625
> 2.25 ** 3.25
13.95060955069482
> pi
3.141592653589793
> 2 * pi * 7
43.982297150257104

Observat, i că sunt acceptat, i tot, i operatorii matematici obis, nuit, i, iar ordinea operat, iilor
este cea cunoscută de la matematică (ı̂nmult, irea are prioritate ı̂n fat, a adunării). Paran-
tezele pot fi folosite pentru a schimba ordinea efectuării operat, iilor. Numerele prefixate
de un - (minus unar) trebuie puse ı̂ntre paranteze când apar ı̂ntr-o expresie mai com-
plicată (ce se ı̂ntâmplă altfel?). Operatorul ** este operatorul de exeponent, iere pentru
numere fract, ionare, ı̂n timp ce ^ este operatorul de exponent, iere pentru numere ı̂ntregi.
Pentru numerele ı̂ntregi, Haskell foloses, te implicit o precizie infinită (limitată doar de
memoria disponibilă).
Exercit, iu. Încercat, i s, i alte expresii care combină operat, iile prezentate mai sus.
În afară de operatorii matematici de mai sus, expresiile Haskell pot cont, ine apeluri
la funct, ii. În limbajele tradit, ionale, cum ar fi C sau Java, sintaxa de apel a funct, iilor
este: se scrie numele funct, iei, se scrie ’(’, se scriu parametrii separat, i prin virgule, se
scrie ’)’. Spre deosebire de limbajele tradit, ionale, sintaxa de apel a funct, iilor este mai
simplă, des, i init, ial poate părea cam ciudată. În Haskell, sintaxa de apel a unei funct, ii
este: se scrie numele funct, iei, se scrie un spat, iu, se scriu parametrii, separat, i prin spat, ii.
Mai jos sunt câteva exemple de apeluri de funct, ii. Funct, ia div calculează câtul
ı̂mpărt, irii ı̂ntregi iar funct, ia mod restul ı̂mpărt, irii. Funct, ia sin calculează o aproximare
a funct, iei sinus cunoscută de la matematică.

> div 10 2
5
> div 9 2
4
> div 100 7
14
> mod 100 7
2
> 14 * 7 + 2
1.3. EXPRESII SIMPLE ÎN HASKELL 11

100
> sin 3.14
1.5926529164868282e-3
> sin pi
1.2246467991473532e-16

Observat, ie. Constanta predefinită pi cont, ine o aproximare a numărului π = 3.14 . . . ∈


R. În Haskell, nu există nicio diferent, ă ı̂ntre o funct, ie cu 0 parametri s, i o constantă. Din
acest punct de vedere, expresia Haskell pi poate fi văzută ca apelul funct, iei pi, aplicată
peste 0 argumente.
Cum calculăm, folosind funct, ia div, câtul ı̂mpărt, irii (câtului ı̂mpărt, irii lui 100 la 7)
la 3? Conform sintaxei de mai sus, am putea ı̂ncerca:

> div div 100 7 3


... [eroare]

Dacă ı̂ncercat, i să evaluat, i expresia de mai sus, vet, i obt, ine o eroare de tip. Deo-
camdata nu vom ı̂ncerca să ı̂nt, elegem eroarea, dar intuitiv trebuie să s, tit, i că sistemul
ı̂ncearcă să apeleze funct, ia div pe patru argumente: div, 100, 7 s, i respectiv 3. Deoarece
funct, ia div nu as, teaptă decât două argumente, amândouă numere ı̂ntregi, apare eroare
de tip. Acestă eroare se ı̂ntâmplă din cauza modului de parsare a apelurilor de funct, ie.
Putem folosi paranteze pentru a fort, a interpretarea pe care o dorim:

> div (div 100 7) 3


4

În general, apelurile de funct, ii care apar ı̂n interiorul unei expresii mai complicate
trebuie marcate prin paranteze pentru a obt, ine efectul dorit:

> (div 100 7) * 7 + (mod 100 7)


4
> sin sin 3.14
... [eroare]
> sin (sin 3.14)
1.5926522431813962e-3

Deoarece parantezele pot fi folosite ı̂n mod liber ı̂n jurul unor expresii, există tendint, a
printre ı̂ncepători să parantezeze funct, iile unare pentru a obt, ine o sintaxă similară cu
sintaxa limbajului C:

> sin(sin(3.14))
1.5926522431813962e-3

Acest lucru trebuie evitat, deoarece acest mod de scriere a codului nu este idiomatic
s, i poate genera confuzii pe viitor (de exemplu, nu se poate generaliza la funct, ii cu mai
multe argumente).
12 CHAPTER 1. INTRODUCERE ÎN HASKELL

Gres, eală frecventă


O problemă frecventă, cauzată de folosirea gres, ită a parantezelor (s, i incurajată
de folosirea neidiomatică a parantezelor), este ilustrată ı̂n următorul exemplu:
> div(10 3)
... [eroare]
Programatorul a amestecat sintaxa de apel a funct, iilor ı̂n Haskell cu sintaxa
de apel a funct, iilor ı̂n C(++), rezultatul nefiind corect din punct de vedere
sintactic ı̂n niciul dintre limbaje. Un programator Haskell experimentat nu ar
folosi niciodata ( imediat după numele funct, iei (fără să lase un spat, iu) s, i ar
ı̂nt, elege imediat eroarea:
> div (10 3)
... [eroare]
Pentru a ı̂nt, elege mai bine de ce linia precedentă cont, ine o eroare, observat, i
că funct, ia div este apelată pe un argument, s, i anume rezultatul ı̂ntors de
funct, ia 10 pe argumentul 3 (doar că 10 nu este funct, ie...)! Corect ar fi:
> div 10 3
3

Exercit, iu. Încercat, i s, i alte expresii mai complicate s, i ı̂ncercat, i să minimizat, i numărul
de paranteze necesare pentru a obt, ine rezultatul dorit.
În Haskell, True s, i False reprezintă cele două valori de adevăr. Funct, ia not cal-
culează negat, ia logică, ı̂n timp ce operatorii && s, i respectiv || calculează “s, i”-ul logic s, i
respectiv “sau”-ul logic:

> True
True
> False
False
> not True
False
> not (not True)
True
> not (not (not False))
True
> True && True
True
> False && True
False
> False || True
True
> not (False || True)
1.4. TRUCURI SINTACTICE 13

False

Valorile pot fi comparate folosind operatorul == (pentru egalitate) s, i operatorul /=


(pentru disegalitate):

> 42 == 42
True
> 41 == 42
False
> 42 /= 9
True
> 42 /= 42
False
> True == True
True
> True == False
False

Dacă ı̂ncercăm să comparăm două elemente de tipuri diferite, vom obt, ine o eroare
de tip (deocamdata nu ı̂ncercăm să o ı̂nt, elegem):

> 5 == True
... [eroare]

1.4 Trucuri sintactice


În Haskell, orice operator binar poate fi transformat ı̂ntr-o funct, ie s, i orice funct, ie binară
poate fi transformată ı̂ntr-un operator folosind următoarele transformări:

1. Dacă o este un operator binar (folosit ı̂n format infix), atunci (o) este o funct, ie
binară s, i x o y este aceeas, i expresie cu (o) x y;

2. Dacă f este o funct, ie cu două argumente, atunci ‘f‘ se comportă ca un operator


ı̂n format infix: x ‘f‘ y este o expresie identică cu f x y.

Atent, ie! În itemul al doilea, caracterul ‘ (numit backtick, cod ASCII 96) este diferit
de caracterul apostrof (’) s, i de caracterul ghilimele ("). Acest caracter se găses, te de
obicei la stânga tastei 1 sau, la unele calculatoare, ı̂ntre tasta Shift s, i tasta Z.
Mai jos sunt câteva exemple de folosire a notat, iilor de mai sus:

Prelude> (+) 5 3
8
Prelude> (-) 5 3
2
Prelude> (-) 3 5
14 CHAPTER 1. INTRODUCERE ÎN HASKELL

-2
Prelude> (*) 4 2
8
Prelude> (*) 4 ((+) 2 3)
20
Prelude> 4 ‘div‘ 2
2
Prelude> 100 ‘mod‘ 7
2
Prelude> (+) (100 ‘mod‘ 7) (7 - 2)
7

Observat, ie. Cele două notat, ii nu se pot folosi simultan. Astfel, textul (‘div‘) s, i
textul ‘(+)‘ vor genera eroari de sintaxă.
Pentru a evita aglomerarea cu paranteze, se poate folosi uneori operatorul $:

> sin (sin (sin 3.14))


1.592651569876818e-3
> sin $ sin (sin 3.14)
1.592651569876818e-3
> sin $ sin $ sin 3.14
1.592651569876818e-3

Situat imediat după o funct, ie (cu un singur argument), acest operator fort, ează ca
toată expresia ce ı̂ncepe imediat după $ s, i se termină la sfârs, itul liniei (sau până la o
paranteză ı̂nchisă) să fie considerată argumentul funct, iei ı̂n cauză.

S-ar putea să vă placă și