Sunteți pe pagina 1din 312

.

Papa

INTRCDUCERE f N

ft~SKELL 98
PRIN EXEMPLE

qsort [] = []
qsort (x:xs)
= qsort pre ++ [x] ++ qsort post
where
pre = [ n n< - xs, n < x 1
post = [ n n <· xs, n >= x 1
0 introducere in Haskell 98
prin exemple

Dan V. Popa, popavdan@yahoo.com

EDITURA ALMA MATER, 2016


Descrlerea CIP a Blbliotecii Natfonale a RomAnlel
POPA, DANV
0 lntroducere i n Haskell 98 prln example I Pops V. Dan. - Ed. a
;2-a, rev. - Bacau : Alma Mater, 2015
Bibliogr.
ISBN 978-606-527-506-5

~04.43 HASKELL 98
Cuprins

CuvAnt lnainte ; Foreword .................................................................................... 11


lntroducere ......................... ................................................................................ 15
1. Functii $i aritmetic:. ....................................................................................... 17
1.1. Tipurile Integer, Bool, Char $i altele; Tipurile compuse, liste $i perechi 20
1.2. Operatorii (operatiile) ............................................................................... 23
1.3. Functiile. Un prim exemplu , functia increment ....................................... 26
1.4. Rularea unui progream cu hugs pe un sistem Linux .............................29
1.5. Despre variabilele folosite Ia signaturile functiilor $i semnul -> ............... 31
1.6.Suma a dou:. numere ................................................................................ 32

2. Pe urmele functiilor care manipuleaz:. functii. ........................ .......................... 36


2.1. Functii care manipuleaz:. functii : Sumarea;
Functia care aplic:. o alta functie de doua ori. ............................................. 38
2.2.Manipularea repetata a functiilor: Recursivitatea; ..................................... 39
Exemple: Factorialul $i functia lui Ackerman-Peter....................................40
2.3. Operatorul "suma" - o prima lncercare nereu$ita (deoarece Haskell 98 nu
permite variabile ale carer nume se repeta i n acela$i $ablon) .. ..................... 44
2.4.Pattern-uri $i pattern-uri cu gard:.
Sign- semnul unui numar .........................................................................47
2.5. Sigma ref acut ...........................................................................................49
2.6.Factorialul refacut. .....................................................................................51
2.7. Despre o suma de zerouri sau despre lambda expresii $i despre
$ablonul pentru orice (_} ........................................................................... 54
2.8.Cum putem folosi lambda expresiile ........................................................... 59
2.9. Lambda expresiile cu parametri multipli ...................................................60

3. Cat mai multe despre tipuri $i crearea lor....................................................... 62


3.1. Un vechi exemplu: Triunghiul,declaratia data............................................62
3.2.Tipuri utilizator $i reuniunea ....................................................................... 68
3.3.Tipuri recursive $i arbori polimorli ..............................................................71
3.4.Liste in Haskell ...........................................................................................76
3.5.Liste $i iar liste, liste de caractere, string uri ............................................... 85
3.6. Tipuri sinonime, declaratia type ..............................................................91
3.7. Multimi implicite reprezentate ca liste ........................................................93
3.8. Secvente aritmetice...................................................................................96

4. Structuri de date infinite $i alte notiuni utile ......................................................98


4.1.Structuri de date infinite .............................................................................98
4.2.Functii care manipuleaza liste, extrase din Standard Prelude.................. 108
4.3.CASE $i IF in Haskell apoi LET... IN ... .................................................. 113
4.4. ~i ... WHERE ... , un tel de LET... IN... scris invers ................................ 121

5. Reprezentarea unui limbaj in Haskell.. ....................................................... 124


5.1. Sintaxa (abstracta) ................................................................................. 125
5.1.1.Un mic interpreter ............................................................................... 129
5.2.Cum exprimam valorile rezultate ............................................................ 136
5.3.Cum reprezentam memoria sau asocierea variabile-valori ..................... 138
5.4. Compunerea valorilor,evaluarea termilor,pas $i proces de calcul. ...... 140
5.5.Semantica $i interpretare .......................................................................... 142
6. Monada .......................................................................................................... 145
6.1.Monada- scurta prezentare (de interes istoric) ....................................... 148
6.2.0 lucrare celebra ~i notatiile de atunci, ..................................................150
6.3.Functia polimorfica "unitM" ....................................................................... 154
6.4.Functia polimorfica "bindM" ...................................................................... 156
6.5.Transcrierea semanticii In do-notatie ........................................................ 158
6.6.0peratori de lifting ................................................................................... 160
6.7.0bservatii si concluzii .............................................................................. 163
6.8 Un interpreter rescris ~i comentat, realizat cu o instanta a clasei Monad164

7. 7 Monade utilizate de programatori ................................................................ 179


7.1. Monad a identitate .................................................................................... 179
7.2. Monada de 1/0......................................................................................... 180
7.3. Monada starilor........................................................................................ 182
7.4.Monada cu stari si (string de) 1/0 ............................................................. 186
7.5.Monada listelor......................................................................................... 187
7.6. Monada Maybe ........................................................................................ 189
7.7.Monada parserelor................................................................................... 190

8.Studiu de caz: evaluatorul de expresii ............................................................ 194


9.Studiu de caz : generatorul de cod ................................................................ 222
9.1.Doar doua vorbe despre lazy evaluation .....................................222
9.2. Pregatim monada starilor ~~ formatul instructiunilor .................... 224
9.3. Compilarea constantelor .............................................................232
9.4. Compilarea variabilelor ... ........................................................ .... 234
9.5. Compilarea declaratiilor variabilelor ............................................236
9.6. Compilarea instructiunii skip .......................................................238
9.7. Compilarea instructiunii read ...................................................... 240
9.8. Compilarea instructiunii write ...................................................... 241
9.9. Compilarea instructiunii de atribuire ........................................... 244
9.10. Compilarea operatiilor din expresii ...........................................245
9.11. Compilarea comparatiilor din expresii ......................................249
9.12. Compilarea instructiunii conditionale (if) .. ................................ 250
9.13. Compilarea instructiunii structurate: secventa ......................... 254
9.14. Compilarea unei instructiuni ciclice: bucla while ...................... 256
9.15. Compilarea unei instructiunii ciclice: bucla do-while ................ 259
9.16. Compilarea programului principal ............................................262
9.17. Compilarea unui program principal: exemplul dat ca test ....... 267

Anexa A : lnstalarea Hugs98 pe o ma$inA Windows ......................................... 270


Anexa B : lnstalarea Hugs pe un sistem PCLinuxOS 9 ..................... ................ 280
Anexa C : lnstalarea Hugs din distributia Mandrake 10.0 pe o ma$inA Linux
Mandriva 2005 ..................................................................................................283
Anexa D: lnstalarea Happy pe o ma$inA Linux Mandriva 2005 ................... 284
Anexa E: Instal area hugs pe sistemele Ubuntu ................................................ 287
Anexa F: ProprietAti algebrice ale monadei parserelor - caz particular de monadA
cu "plus" $i "zerou"............................................................................................. 289
Anexa G : Schema interpretorului monadic ...................................................... 290
Anexa H: Cal/ by value interpretorul (din finalul cap. 6) in do-notatie ..............292
Anexa 1: intrebAri de control .............................................................................296
Bibliografie ........................................................................................................302
In anti '98, Ia data aparipei standardului Haskell 98 (al limbajului Haskell), in
Romama nu se folosea practic deloc limbajul, modem ~i funcponal, cunoscut sub
numele profesorului Haskell. Lucrul era de inteles fiindca de~i limbajul ~i prima sa
implementare (Gofer) existau in laboratoare de aproape un deceniu, standardul
cunoscut sub numele de Haskell 98 ~i volumul sau de referinta nu aparusera Inca sau
erau abia in curs de a se publica. Se intampla, ici ~i colo, pe cate-un forum, sa apara
cate-un curios care intreba ce este acest limbaj numit Haskell, dar nu-i raspundea
nimeni.

Autorul acestei carp a inceput deci pregatirile pentru o teza de doctorat studiind
lucran scrise in Haskell un limbaj care era nou-nout (ceea ce a dus Ia planul de a-i
scrie un manual original, in limba romana) avand ca unice resurse un CD al
distributiei franceze Mandriva Linux 8.2 ('inlocuit ulterior de cele de Ia versiunile 9.0
~i 10.0 ~.a.m.d) oferit (fiecare) de revista CHIP ~i pe care se afla, printre alte pachete
cu free software ~i interpretorullimbajului Haskell, numit Hugs.

0 alta resursa care ne-a fost de folos in acele vremuri a fost versiunea engleza a unui
exemplar al carpi ,,A Gentle Introduction to Haskell 98" semnata de cei trei mari
special~ti in Haskell de Ia Yale ~i Los Alamos: Paul Hudak, John Peterson, Joseph H.
Fasel.

Cam in aceea~i perioada, cu acordul Prof. Simon Peyton Jones am deschis pentru
publicul roman o pagina web cu intrebari ~i raspunsuri despre Haskell, marcand astfel
na~erea a ceea ce se nume~te Ro/Haskell Group, care va ajunge ceva mai tarziu
proiect oficial al Universitatu ,.Vasile Alecsandri" din Bacau. In acel moment nu mai
exista decat un singur grup european (non britanic) creat pe acel site, grupul spaniol.
Aceasta prima pagina s-a transformat in scurt timp 'intr-un site cu de sute de pagini,
cu zeci, sute ~i apoi mii de accese pe luna. Este vorba de
www.haskell.org/haskellwiki/RofHaskell. Ulterior, www a fost redenumit wiki. Iar
adresa a devenit https://wiki.haskell.org/Ro/Haskell.

Site-ul a oferit (~i ofera inca,) raspunsuri la intrebari despre limbajul Haskell
dinaintea publicarii edipei romane a carp_i celor trei autori - menponap mai sus - de
catre editura Matrix Rom ~i dinaintea publidirii primului manual romanesc de
Haskell de ditre editura locala, bacauana, Edusoft. Acest prim manual de Haskell a
fost scris ulterior realizarii primei parp a site-ului.

Volumul acesta, pe care il tinep acum in mana este primul manual de Haskell din
Romania, republicat acum mtr-o editie revizuWi ~i adaugita, prima edipe fiind de
mult epuizata. Capitolul noua, care i s-a adaugat proviDe dintr-o cercetare despre
construqia compilatoarelor desfasurata tot Ia Univ. ,Vasile Alecsandri" din Badiu.
Autorul a lucrat la aceste materiale, atunci ~i acum, din pura pasiune, fara nici un
onorariu special pentru aceste proiecte ~i transrnite sincerele sale multumiri tuturor
celor care au avut curajul de a-~i folosi capitalul ~i munca in aceasta ambipoasa ~i
riscanta intreprindere: promovarea intr-o Romanie pe atunci cantonata m epoca
limbajuluj LISP (un limbaj funcponal slab tipizat, interpretat ~i lent) a unui Limbaj
funcponal pentru industrie ~i cercetare revoluponar: Haskell este un Hmbaj functional
puternic tipizat, compilabil cu compilatorul GHC - Glasgow HaskelJ Compiler - ~i
mult mai productiv.

Sunt destinatari ai acestor mulfUmiri top cei care, la editurile care ne-au sustmut ~i
publicat, au contribuit, fiecare dupa posibilitap, cu munca, cu grafica, cu critica sau
cu capitalulla aparitia aces tor editH ale carpi or des pre Haskell.
Cuvant inainte

Haskell este unul dintre cele mai pasionante limbaje de programare.


Fiind un limbaj functional pur, el abordeaza lntr-o maniera eleganta
~i radicala problema scrierii programelor sigure. Mai mult decat atat,
in ultimii ani, Haskell s-a transformat dintr-un fascinant limbaj
academic lntr-o unealta pe care multi programatori o folosesc
deoarece ofera cea mai rapida cale de realizare a proiectelor lor.
Daca mai aveti dubii alaturati-va celor de pe canalul de IRC #haskell
sau celor de pe lista de e-mail-uri Haskell-cafe ~i veti fi in contact
direct cu entuziasta ~i binevoitoarea comunitate de utilizatori de
Haskell din toata lumea.
La un nivel avansat, pe masura ce scriem programe mai mari, lndata
ce paralelismul devine important, consider ca avantajele controlului
asupra efectelor laterale devin de importanta crescanda. Haskell
este lider ~i aici iar acum putem deja vedea idei din lumea
programarii functionale fiind adoptate de catre limbajele de larga
circulatie din "mainstream". Exemplele includ tehnica "garbage
collection", polimorfismul parametric, descrierile, evaluarea intarziata
~i multe altele.
Este momentul propice pentru a deveni expert In limbaje
functional e.

Profitati de o asemenea carte!

Simon Peyton-Jones

0 introducers in Haskell 98 prin example - 11 - Dan Popa


J ,,

~j

0 introducere Tn Haskell 98 prin exemple - 12 - DanPopa

.j

Ill
Foreword

Haskell is one of the world's most exciting programming languages.


Being a purely-functional language, it takes a radical and elegant
approach to the challenge of writing programs that work.

Moreover, in the last few years, Haskell has moved from being an
fascinating academic language to being a tool that many
programmers use because it's the fastest way to get the job done.

If you doubt this, join the #haskell IRC channel, or the Haskell-cafe
mailing list, and you will find a vibrant, enthusiastic, and helpful
community of Haskell users.

At a deeper level, as we write larger programs, and as parallelism


becomes more important, I think that the advantages of controlling
side effects will become increasingly important. Haskell is leading
the way, and we can already see ideas taken from the functional
community being adopted in mainstream languages. Examples
include garbage collection, parametric polymorphism,
comprehensions, laziness, first-class functions, and much more
besides.lt's an exciting time to be a functional programmer. Enjoy
this book!

Simon Peyton-Jones

0 lntroducere In Haskell 98 prin exemple - 13 - Dan Popa


lntroducere

Haskell este un limbaj de uz general, din categoria limbajelor functionale


pure (f~r~ instructiuni imperative sau produc~toare de efecte laterale).
Folose$te Lazy Evaluation. Beneficiaz~ de un sistem de tipuri
revolutionar, cu tipuri polimorfice $i clase de tipuri ceea ce inseamn~ c~

veti putea programa in comodul stil numit "generic programming" cu


arbori $i liste de orice tel iar codul va fi U$Or reutilizabil. Haskell permite
programarea cu clase $i instante de clase definite utilizator, clasele fiind
multimi de structuri algebrice (adic~ mai mult decAt multimi de obiecte).

Haskell este deosebit: El are o sintaxa bid ime nsional~ (bazat~ pe


notiunea de Layout) a$a c~ simpla aliniere corect~ pe vertical~ v~

scuteste de parantezellar dac~ puneti un spatiu in PLUS nu eo gre$eal~ .

Sintaxa bidimensional~ face programele mult mai clare dec~t

echivalentele lor in LISP (netipizat $i cu multe paranteze: LISP = Limbajul


lnSuportabilelor Paranteze) sau cele in Scheme orl ML. Alta plusuri sunt
tipizarea strict~. existenta monadelor $i a do-notatiel (o scriere cvasi-
imperativ~ posibll~ intr-un limbaj functional pur) precum $i existenta
listelor infinite $1 a multimilor ordonate definite descriptiv, numite in
manualele de Haskell (de llmba englez~) "list comprehensions". Listele
infinite $i posibilit~tile de a le descrie $1 manipula in Haskell sunt de
asemenea apreciate, mai ales de matematicienii care folosesc Haskell ca
instrument in domeniul matematicilor discrete.

Productivitatea program~rii in Haskell este net superioar~ alter limbaje, un


algoritm Quicksort scriindu-se in doar 3-4-5 linii de program nu i n 30-40

0 introducere rn Haskell 98 prin example - 14 - Dan Popa


ca in C sau C++. lar un interpretor de limbaj sau un translator pentru
structuri de date compuse avAnd sintaxa data de o gramatica
independenta de context se poate implementa intr-o singura zi datorita
bibliotecilor de combinatori de parsere $i do-notatiei.

Unul dintre motivele pentru care Haskell este atat de productiv este
posibilitatea de a Iuera cu functii de nivel superior, polimorfice, operAnd
inclusiv pe structuri de date definite inductiv. Acele structuri de date
beneficiaza de functii de 110 $1 comparare ($i nu numai) generate automat
de sistem prin "deriving" ori definite de utilizator. Functiile de nivel
superior pot incapsula intregi $abloane de programare a aplicatiilor luand
locul acelor $abloane de proiectare cu care v-ati batut capul in alte
cursuri . Doriti o aplicatie dupa acela$i $ablon ? Luati functia de nivel
superior dorita $i dati-i ca parametri alte functii, specifice aplicatiei.

V-ati batut capul nu o data sa rescrieti rutinele de afi$are ale unor structuri
de date compuse (liste de arbori cu nod uri perechi de .. . $am d) care se
schimbau pe masura ce dezvoltati proiectul? Ati refacut mereu functiile de
comparatie pentru astfel de structuri de date compuse? Folositi Haskell $i
adaugati declaratiilor de date "deriving Show" respectiv "deriving Eq" (lar
lista nu se opreste aici.) Haskell face automat restul iar tipul nou creat
capata din cllpa declararii functiile de afi$are sau comparare ce-i sunt
necesare tara sale mai scrieti dumneavoastral Dar le pute~ rescrie daca
nu va place efectul calor implicit generate.

Exista o multime de compilatoare $1 interpretoare pentru Haskell $i pe


deasupra sunt disponibile gratuit. Hugs e un interpretor bun pentru
primele lectii de Haskell. GHC este un compilator pentru proiecte
profesionale. Cititi va rog $i paragraful despre: lnterpretoare $i

0 introducere in Haskell 98 prin example - 15 - Dan Popa


compilatoare de Haskell disponibile, de pe pagina comunitatii romAne a
utilizatorilor de Haskell Ia adresa
http://www.haskell.org/haskellwiki/Ro/Haskell. Consultati ~~ anexele
acestei carti.

Dac~ v~ intereseaz~ teoria automatelor $i limbajelor formale sau


metode de transcriere a structurilor de date dintr-un format in altul,
Haskell este un instrument excelent. Un translator de Ia o sintax~ Ia
alta se scrie uimitor de repede, in cateva ore. Ba chiar $i un mic
limbaj de programarel Dar nu uitati mai inainte s~ citi1i ceva despre
teoria limbajelor formale. Un capitolul dedicat gramaticilor de tip 2
(Context Free Grammmars) din ierarhia lui Chomsky in primul rand
dar $i celelalte lucr~ri ale lui Noam Chomsky.

0 serie de alte proiecte sunt in derulare: de Ia software pentru


muzic~ Ia compilatoare $i interpretoare de limbaje (cum este Perl 6),
de Ia roboti de ire Ia servere web $i pin~ Ia jocuri video $i multimedia
ori acces Ia baze de date, practic totul se face cu succes in acest
limbaj revolutionar care este Haskell.

Dan Popa
Bacau, ianuarie 2007-2014

0 introducere in Haskell 98 prin exemple - 16 - Dan Popa


Functii ~i aritmetica

Despre: c~teva intrebari pe care vi le puneti atunci c~nd abordafi un


nou limbaj, tipurile de date (arhi)cunoscute din alte limbaje, Tipuri/e
compuse, liste $i perechi, operatorii (operafiile), Functiile, functia
increment, Rularea unui program, variabilele folosite Ia signaturi/e
functiilor $i semnul ->

..2Qspundem in acest capitol Ia cateva intrebari pe care vi le puneti

atunci cand abordati un nou limbaj. Haskell este un limbaj functional


pur, dar in care se poate programna ~i in stil imperativ (vom vedea
ca se folose~te o scriere speciala numita do-notatie asemanatoare
cu scrierea din C sau Pert ). in Haskell 98 exista de asemenea o
serie de notiuni specifice programarii orientate obiect cum ar fi clasa,
instanta a unei clase etc. Functiile, vom vedea, sunt polimorfice. Se
pot descrie U$Or de tot liste ~i arbori, reuniuni de tipuri ba chiar
multimi ordonate $i liste infinite. (lnteresant, nu-i a~a ?) $i totul se

0 introducere in Haskell 98 prin example - 17 - DanPopa


scrie rapid $i compact (un algoritm Quicksort lncape In doar patru-
cinci rAnduri de program I) $i f~r~ aglomerarea aceea de paranteze
cu care ne-au fortat s~ ne obi-$nuim limbaje ca Lisp (poreclit de unii
"Limbajul lnSuportabilelor Paranteze") sau ML. Toate aceste
concepte le vom prezenta rand pe rand, r~spunzand una cAte una
Ia ni$te lntreb~ri pe care $i le pune eel care invat~ un limbaj de
programare. incepem cu cele mai simple lucruri, cum ar fi lntrebarea
dintai:
- Cum se scriu In Haskell tipurile de date (arhi)cunoscute din alte
limbaje: lntregii, numerele reale $i alte tipuri comune ?

R~spunsurile vor fi detaliate, fiecare subcapitol dintr-un capitol fiind


centrat unei lntreb~ri sau tratand, exhaustiv ori uneori tangential,
cate un subiect, caz in care subiectul este ulterior reluat intr-un alt
capitol. Subiectul este anuntat de titlul subcapitolului in cauz~.
De exemplu, In capitolul I vom nlspunde printre altele Ia lntreb~ri

cum ar fi :
- Cum se scriu In Haskell operatorii ?
- Cum se scrie o functie simpl~ ?
- Cum se ruleaz~ un program ?
Ulterior vom trace Ia o serie de example, scriind mai inUli simple
functii in Haskell, $i trecand gradat Ia example mai complicate. Tn
primul capitol ne vom ocupa de ni$te example care ilustreaza:
- Felul cum se scriu functiile
- Functia increment
- Functia factorial

0 introducere rn Haskell 98 prin exemple - 18 - Dan Pops


'_j

Vom explica $i cum veti rula aceste exemple pe computerul


dumneavoastr~ dotat cu sistem de operare Linux. La realizarea
acestei carti am folosit Mandrake Linux 8.2 (iar ulterior am trecut Ia
Mandrake Linux 10). Puteti folosi orice distributie cu conditia s~ aveti I

inclus in distributie interpretorul Hugs sau sa-l instalati ulterior. Hugs


este disponibil atat ca arhiva zip cat $i ca pachet .rpm. Teoretic
vorbind, aceste exemple pot fi rulate $i pe o ma$ina Windows pentru
care detineti software-ul necesar, o implementare de Haskell aliniata
Ia standardul Haskell 98. Utilizatorilor unor versiuni mai vechi le este
recomandat sa treaca Ia Haskell 98, versiunile mai vechi fiind
actualmente dep~$ite .

1.1. Tipurile Integer, Bool, Char '' altele; Tipurile compuse, liste

'' perechi

Atunci cand incepem invatarea unui limbaj, deoarece limbajul este


ceva destinat pentru a programa un calculator sa prelucreze ni$te
date, unul dintre primele lucruri care ne-ar interesa este ce tel de n

n
date putem prelucra cu acellimbaj $i cum se descriu ele. De obicei
ne intrereseaza sa prelucram date din ni$te tipuri clasice: 7ntregii,
numerele reale, valorile logice (numite uneori valori booleene),
simbolurile tipografice cunoscute sub denumitrea de "caractere" $i
citatele intre ghilimele, numite stringuri. lata cum se numesc aceste
multimi de date - tipurile citate mai sus, in Haskell 98:

Numerele intregl se numesc Integer sau Int.

0 introducere in Haskell 98 prin example - 19 - DanPopa

1
Numerele reale sunt de mai multe feluri ,retineti deocamdata tipul
Float.
Valorile logice, booleene, doua Ia numar (True ~i False) formeaza
tipul Boo/.
Caracterele tipografice sunt elemente din tipul (care include ~i
simbolurile), Char.
Stringurile sunt de fapt liste de caractere iar tipul string se noteaza
In consecinta ca un tip compus: [Char]. Parantezele patrate
avertizeaza ca este vorba de lista de elemente de tipul descris In
paranteza, actionand deci ca un constructor de tipuri (cum era
perechea RECORD ... END In Pascal ~i Oberon numai ca aici se
declara liste nu structuri).

Notati ~i faptul ca In biblioteca ce se lncarca Ia pornirea


interpretorului (pe care v-o puteti imagina deocamdata ca un fel de
<Stdio.h> din C, cu toate ca In realitate ea ofera mult mai multe
lucruri), biblioteca numita "Standard Prelude" sau mai pe scurt
"Prelude" veti gasi ~i alte tipuri fie ele simple fie veritabile clase . Va
invitam sale descoperiti. Cautati cuvinte ca Rational, Double ...

Retineti: Cele mai simple tipuri de sunt:

Integer (sau lnt) , Boo/, Float, Char

iar stringurile formeaza tipul [Char} denumit cu aliasul String.

0 introducere in Haskell 98 prin exemple - 20 - Dan Popa


Deocamdata nu discutam amanunte subtile despre tipurile de date.
Acestea lnsa exista: de exemplu despre diferenta intra lnt $i Integer
programatorii hAr$iiti v-ar spune ca functile care folosesc pe unul
sunt putin mai mai rapide decat cele care II folosesc pe celalalt.)
Asemenea diferente nu vor fi vizibile In exemplele noastre. De
asemenea, nu punem In discutie aici pe () un fel de tip cu un singur
element notat chiar cu (). Aceasta valoare () va fi folosita Ia operatii
de intrarelie$ire.
Atentie: in Haskell numele tipurilor de date lncep intotdeauna cu
majuscula. Regula este valabila $i pentru tipurile utilizator, motiv
pentru care veti scrie tipul Arbore cu A majuscula, atunci cAnd veti
avea nevoie de un asemenea tip. in schimb functiile se scriu cu
minuscula, de exemplu o functie care incrementeaza o variabila o
veti numi inc. Haskell 98 este un limbaj din categoria celor in care
minusculele $i majusculele au sens diferit, fiind asemenea din acest
punct de vedere cu limbajele C, Java $i Oberon .

Tlpuri compuse: Pe langa modurile de a construi tipuri definite de


utilizator (pe care le vom prezenta in alt capitol), Haskell permite
construirea de liste $i perechi. Acelea$i semne (paranteza patrata
respectiv cea rotunda $i virgula care separa elementele) se folosesc
$i pentru a construi tipul compus din tipuri mai simple dar $i pentru a
construi valoarea compusa din valori mai simple.
Exemple:
[Integer] - lista de intregi iar [1 ,2,3] e lista fermata din 1 2 $i 3,
(Char,lnt) - perechi de Char $i lnt ('b',4) - 'b' $i 4 imperecheate.

0 introducere fn Haskell 98 prln exemple - 21 - Dan Popa


Atentle: Caracterele se scriu intre ghilimele simple, obtinute cu tasta
de langa Enter. Celalalt tel de ghilimele simple, inverse, ce se obtin
cu tasta de sub Escape, sunt folosite in alt scop in Haskell (Ia
definirea operatorilor infixati). Nu le confundati ! Vom remarca ~i

faptul ca stringurile admit o scriere dubla, atat ca liste de caractere


cat ~i ca texte intre ghilimele duble. Ex. ra','l','f,'a1 versus "alta".

Observatie: Elementele unei liste pot fi : - oricat de multe


- dar de acela~i tip, unic
Dar: Elementele unei perechi sunt: - exact doua
- dar pot fi din doua tipuri
diferite dinainte precizate

Vom vedea in capitolele urmatoare ca utilizatorul poate:


- sa introduca noi tipuri ~i noi constructori de tip
- sa redenumeasca tipuri (de exemplu (Char] se poate redenumi
String ~i in acest nume, String chiar se folose~te ca atare)
- sa inventeze noi feluri de date, definind constructorii de date
(deoarece nu este obligatoriu sa se folosesc acela~i nume de functie
pentru a descrie cum se compun atat tipurile cat ~i valorile lor - dar
despre acestea discutam in alt capitol)
- exista ~i alti constructori de tip. Pe linga (), D, exemplele
urmatoare il vor folosi intensiv pe "->" ,eel care construie~te tipul
unei functii.

0 introducere in Haskell 98 prin example - 22 - Dan Popa


u

1.2. Operatorli (operatJIIe)

Operatorii sunt grupati in 10 clase de prioritate. Un operator, chiar ~i

unul definit de utilizator (Da exista a~a o posibilitate in Haskell I)


trebuie plasat intr-una din cele 10 clase de prioritate. in acest fel,
Haskell poate stabili corect ordinea operatiilor dintr-o expresie,
indiferent ce operatori sunt implicati. Unii operatori asociaza implicit
Ia st~nga, altii Ia dreapta. Operatorii infixati care au un nume format
din litera apar in programe cu acest nume incadrat de ghilimele
simple inverse (de pe tasta cu semnul tilda). Regula aceasta este
valabila ~i pentru operatorii definiti de programator. Daca ve~

deschide cu un editor de text fi~ierul

/usr/sharelhugsllib/Prelude.hs (Standard Prelude) veti gasi acolo,


deja declarati, o serie intreaga de operatori. ii vom comenta imediat
pe cei mai multi dintre ei, preciz~nd pentru fiecare printr-o pereche
litera - citra daca e asociativ Ia dreapta (R) sau st~nga (L) ~i ce nivel
de prioritate are.
l1

lata o mica lista de operatori declarati in Standard Prelude :

R9.
L9 II
R8 A I,.,. I**
L7 * , I I 'quof I ' rem' I ' div' I ' mod' 1 :% I%
L6 +I.
R5:

0 introducers in Haskell 98 prin exemple - 23 - Dan Popa

~I
4 ==, 1=, <=, <, >=,>, 'elem', 'notEiem'
R3&&
R21l
L1 >>' >>=
R1 =<<
RO $,$!,'seq'

Recunoa~teti imediat aici: pe nivelul 6 sunt adunarea ~i sc~derea iar


ace~ti operatori asociaz~ Ia stanga. in Haskell a+b+c este deci
inteles ca (a+b)+c . Pe nivelul 7 g~siti inmultirea, imp~rtirea ~i

operatorii care calculeaz~ catul $i restul. Operatorii ' div' $i ' mod'
sunt similari cu omologii lor din Pascal. div face imparti-rea intreaga
$i obtine catul iar mod obtine restul.
Remarcati ca operatorul care implementeaz~ notiunea de "diferit"
este scris in Haskell "/=", spre deosebire de alte limbaje (Pascal, C,
Oberon). Observati c~ relatiile de apartenent~ care exprim~ notiuni
ca "a fi element" si "a nu fi element" au denumiri formate din litere:
· elem' respectiv •notElem'.
Operatiile logice, conjunctia si disjunctia se noteaz~ ca in C si le
g~siti pe nivelele de prioritate 2 si 3.
Operatorii de pe nivelul 1 servesc Ia scrierea operatiilor dintr-o
monada. Aceste structuri algebrice, monadele, provin din teoria
categoriilor si se poate Iuera (nativ) cu ele in Haskell 98 deorece
clasele corespunzatoare sunt predefinite in Prelude. Un exemplu
gasiti in capitolul 6. Poate n-ati $tiut ca, de exemplu, un model de
sistem care face calcule, model in care calculele dau valori si

0 introducere in Haskell 98 prin exemple - 24 - Dan Popa


modificA o memoria (ca atribuirile) se implementeazA imediat in
Haskell ca o monadA.
Nu uitati cA Haskell este un limbaj functional, care opereazA cu
functii de Ia date Ia rezultate (acestea put~nd fi din tipuri diferite). Un
calcul fAcut de un program este p~nA Ia urmA o asemenea functie.
Vom vedea cA operatorul >>= este compunerea in succesiune a
douA calcule. Ceea ce se obtine depinde evident de cele douA
calcule. Operatorul >>= , (pronuntat "bind") exprimA doar felul cum
t

se combinA, cum se succed ele.

- este compunerea functiilor ,~I


',.1
1\
- este ridicarea Ia putere
'quof - similari ca nume cu o functle din Lisp
'div' - impirtirea intreaga
'mod' - restul dat de impartirea intreaga

+'- - adunarea ~~ scaderea, supraincarcate


- cons, adaugarea unui elment in capul
unei liste
==, 1=, <=, <, >=,> -relatiile, comparatllle, supraincarcate
' elem', 'notEiem' - apartenen~ '' neapartenen~ Ia o llsta
(multime)
&& - conjunctia
II - dlsjunctia, (negatia se noteazi cu not )
>> '>>= - operatori pentru monade
(de oblcei inlantulesc calculele fiind
cumva similari lui ";" din limbajele

0 introducere Tn Haskell 98 prin exemple - 25 - Dan Popa

nf 1
imperative. Pot fi redeflnltl in lnstantele
anumitor clase - monad lee - pentru a
obtine diverse efecte)
=<< - Ia fel dar cu operanzll in ordine lnversa

Cu ajutorul lor sunt definiti ~i alti operatori, de exemplu folosindu-se


operatorul ":" (pronuntat "cons" ca in Lisp) este definita
concatenarea a doua liste sau a doua stringuri, notata cu ++ . Veti
folosi frecvent acest operator cand lucrati ++ cu liste sau cu String-
uri. lar cu >== -uri ascunse e definita insa$i do-notatia.

1.3. Functille. Un prim exemplu , functia increment.

Matematicianul care face o declaratie de functie va scrie intai


semnatura acesteia (adica schema de tipuri careia functia i se
conformeaza) . in Haskell ea se nume-$te chiar tipul functiei,
deoarece "->" este $i el un constructor de tipuri, capabil sa imbine
doua tipuri $i sa obtina altul. (Obtinandu-se astfel o ''functie de Ia ...
Ia ...").

Exemplu: f: Z -> Z prin relatia f(x) = x + 1 se scrie in Haskell:


f : : I nteger - > I n teger
f x = x +l
Cap1 Par3Ex1.hs

Pentru a-1 testa, scrieti acest mic program cu un editor de text :

0 introducers in Haskell 98 prin exemple - 26 - Dan Popa


~-- Clj)1Par3Ex' n. AOvancea Ea1tor a
file Edn !lookrnms Iools ~et!Jng ' l:ielp

CH5 ~ 0~ ~ t\1 ~ ~ ~ ~ ~~
f .. Integer - > Integer
f X = X + 1

une 3 cot 1 fiNSr l


Cap1 Par3Ex1 .hs

Puteti alege orice editor doriti din cele disponibile (kwrite, gedit,
emacs, xemacs, vi , pico, nano). La Notepad ++ stabiliti mai lnainte
c~ TAB=8 spatii. Salvati programul sub numele Cap1Par3Ex1 .hs.

Cateva explicatii sunt aici ~i acum necesare: Alinierea pe verticala


este extrem de importanta. In limbaje ca Pascal, C sau Oberon ~i
multe altele, un programator putea scrie tot programul pe un singur
rand, tara ca aceasta sa deranjeze compilatorul sau interpretorul.
Acest lucru nu se poate face In Haskell deoarece Haskell, pentru a
elimina acele paranteze care ne cople:?esc In Lisp sau ML, folose~te
un concept revolutionar: sintaxa bidimensionala. lata ce trebuie s~

:?titi deocamdata despre ea: Trecerea pe randul urmator, dar mai Ia


stanga decat pe precedentul rand echivaleaza cu o lnchidere de
structura sau paranteza. E ca :?i cum s-ar pune automat In Lisp
paranteza lnchisa. (Retineti ca Haskell este un limbaj cu sintaxa
bidimensionalal). Aceasta aranjare In pagina se nume:?te cu
termenul englezesc "layout". Este chiar posibil ca interpretorul sa
semnaleze prezenta incorecta a unei paranteze pe care

0 introducere in Haskell 98 prin exemple - 27 - Dan Popa


dumneavoastra n-ati scris-o, din cauza unei asemenea erori de
layout. In asemenea cazuri verificati alinierea pe verticala ~i aveti
grija ca nu cumva unele r~nduri sa lnceapa mult prea din stanga.
Pare un dezavantaj dar lipsa acelor paranteze din Lisp merita pretul
acesta, dupa parerea mea. Practic, este esential sa aliniati corect pe
verticala tot ce este In interiorul unei substructuri sintactice, sau,
daca sunteti incepator, sa pastrati alinierile pe verticala din
exemplele nostre ~i sa programati cam i n acela~i stil (sau unul mai
bun). Notati ~~ faptul ca, urmare a sintaxei bidimensionale, in Haskell
este important sa puneti spatiile in anumite locuri. Un tab este
considerat egal cu 8 spatii, motiv pentru care trecerea Ia un editor de
text pentru care TAB-ul are alt numar de spatii poate creea unele
problema. Sfatul meu pentru incepatori: Lucrati cu tasta SPACE nu
cu tasta TAB c~nd aveti nevoie de spatii. Extensia fi~ierelor contin~nd
surse de program Haskell este .hs . Mai exista o extensie .lhs pentru
"literate - Haskell" acele texte de lucrari in care totul este comentariu
cu exceptia liniilor care incep cu > ~i formeaza ele insele programul.

Pomlrea lnterpretorulul Hugs.

0 introducere Tn Haskell 98 prin example - 28 - DanPopa


Hugs este numele interpretorului Haskell 98 livrat lmpreun~ cu
distributiile de Unux. Nu este ins~ inclus in toate distributiile oferite
spre download. Eu l-am g~sit inclus in distributiile Mandriva, PC
Linux, Ubuntu etc. il puteti lansa cu una din comenzile:
hugs

hugs <numefi$ier>
hugs ''<numefi$ier>"
Ghilimele sunt necesare dac~ numele de fi$ier contine spatii.
Observati In imaginea de mai sus ~ fiind lansat cu comanda
hugs Cap1 Par3Ex1.hs
interpretorul deschide $1 citeste programe din dou~ fisiere: biblioteca
Standard $i fi$ierul dat de noi In linia de comand~.

in paragraful urm~tor vom explica cum s~ rulati exemplele incluse in


aceast~ carte.

1.4. Rularea unui program

La promptul Linux, care adesea este $ pentru utilizatorul obi$nuit,


tastati:
cd <directorul cu surse>
h ugs <nume program sursa>
Pe sistemul pe care am lucrat pentru a rula acest prim exemplu
trebuia sa scriu:
cd practica-haskell
hugs Cap1Par3Exl . hs

0 introducere in Haskell 98 prin exemple - 29 - DanPopa


Q-.. dan@localhost lhcmetdanJpracttca-haskell- Shell - Konsole
Session Edit View Bookmarks Settings Help

(dan@localhos t -1~ cd practica-h askell


(dan@tocalhost practica-haskettJ~ hugs Cap1Par3Exl.hsl

I"'D • Shell

fncarcarea unul program sursa.

Prima dintre comenzi era necesara numai Ia lnceputul sesiunii de


lucru.
lnterpretorul raspundea lncarcand definitiile din Standard Prelude $i
pe cele din fi$ierul meu, afi$and apoi promptul. (Promptul va fi diferit
de Main doar atunci cand veti Iuera cu module deoarece acest
prompt Haskell indica de fapt numele modulului curent):

Dialog cu lnterpretorullimbajului Haskell.

0 introducere Tn Haskell 98 prin exemple - 30 - Dan Popa


u

I
Acum puteti intreba interpretorul ce valoare are, de exemplu, f (1 00).
in Haskell a$a ceva se scrie tara paranteze:
Main> f 100
lnterpretorul Haskell va raspunde, cum era de a$teptat:
101

Veti lnchide sesiunea de lucru cu interpretorul hugs apas~nd CTRL-


D.

1.5. Despre variabllele folosite Ia signaturile functiilor 'i semnul )

"·>"

Semnul :: se poate citi ca "are tipul" sau "este de tipul".


Semnul -> este tot un constructor de tipuri ca $i cele folosite pentru
liste [ , ] sau perechi de elemente ( , ).
Credeati ca f, definita ca mai sus este o functie de Ia intregi Ia
intregi ? intr-o anumita masura aveti dreptate dar va $i ln$elati.
Definita ca mai sus, f este o entitate de tipul Integer -> Integer a$a
cum perechea ("a",7) este de tipul (Char, Integer). Deci acesta,
lnteger->lnteger este un tip compus, tipul acelor entitati care primind
un argument Integer dau ca rezultat o valoare din tipul Integer. Ele
sunt functiile (de fapt reprezentarile functiilor) de Ia intregi Ia lntregi.

0 introducere in Haskell 98 prin exemple - 31 - Dan Popa

r
1.6. Suma a doua numere

Aplicarea unei tunctii asupra argumentelor sale se scrie in Haskell


tara paranteze. Simplu: se scrie numele tunctiei apoi argumentele in
ordine. De ce se scrie tara paranteze, vom vedea mai bine dupa un
experiment tacut cu tunctia din urmatorul exemplu:

Suma a doua numere


Dan Popa, 30 martie 2005

suma : : Integer -> Integer -> Integer


suma a b = a+ b

Cap1 Par5Ex1 .hs

Editati textul cu editorul dumneavoastra favorit (in imaginea care


urmeaza este kwrite, ruland sub KDE pe Mandriva Linux):

~- .. Cap1 Par5Ex1 hs - Advanced Editor


Eile Edit _llookmalt.s Iools S,e1tings .!:ielp

D6& 0 c9 tt1 ~ X~!l!t ~ ~~


-- Suma a doua numere
-- Oan Popa 30 martie 2005
I
SUIIa -- Integer - > Integer - > Integer
SUIIa a b =- -a + b

Line 3 Col: 1 I INS I I

Suma a doua numere.

0 introducers in Haskell 98 prin example - 32 - Dan Popa


Primele doua rAnduri sunt comentarii. Acestea lncep cu doua
semne "-" $i pot contine orice text, care continua pAna Ia capatul
liniei. Schimbarea acestor texte nu influenteaza , evident, modul de
executie al programului. Salvati fi$ierul cu numele Cap1 Par5Ex1 .hs
$i reporniti interpretorul cu comanda:

hugs Cap1 Par5Ex1 .hs

Puteti verifica cum se calculeaza suma, tastAnd Ia promptul Main:


Main> suma 1 2

lar interpretorul va raspunde cum era de a$teptat


3

Surpriza: La lntrebarea, pusa interpretorului, de a evalua expresia:


sum a 1, sistemul calculeaza ceva $i afi$eaza un mesaj de eroare,
nereu$ind sa va scrie pe ecran ce rezultat a obtinut:

ERROR - Cannot find "show" function for :


• • Expresion suma 1
.. Of type Integer -> Integer

Dezvaluim un secret, show este functia folosita de interpretorul hugs


pentru a afi$a rezultatul unui calcul. Sunt de fapt mai multe functii "show"
fiecare pentru un anumit tip de date. De exemplu , Ia afi$area tntregilor din
tipul lnt se folose$te o functie care s-ar putea numi "showlnr $i ar avea
tipul, semnatura: lnt -> String. Lucrul este de a$teptat deoarece pe ecran

0 introducere fn Haskell 98 prin exemple - 33 - Dan Popa


se scriu pan~ Ia urm~ stringuri.

Totu~i ce inseamn~ mesajul ? El anunt~ cas-a evaluat expresia suma 1


~i s-a obtinut un rezultat de tipul Integer -> Integer. Este perfect normal,
deoarece suma era de tipul Integer -> Integer -> Integer iar atunci cand i
s-a transmis valoarea primului argument (evident i i trebuia un intreg din
tipul Integer) i-a r~mas rezultatul de dup~ primul Integer-> adic~ Integer
->Integer.
Not~ : Declaratia functiei suma de mai sus este echivalent~ cu :

Suma a doua numere


Dan Popa , 30 mart i e 2005

suma :: Integer -> (Integer - > Integer )


suma a b = a + b

Retineti $i faptul c~ in definitia semnaturii unei functii se pot folosi $i


parantezele, rotunde cu semnificatia obi$nuita. De asemenea,
impreuna cu -> se pot folosi $i parantezele care au semnificatia de
constructori de tipuri , e verba de paranteza patrata care construie$te
liste [ ] $i cea rotunda cu virgula ( , ) care construie$te perechi. Vom
vedea ca inafara de -> [ ] $i ( , ) exista $i constructori de tip care au
nume scris cu litera $i al carer nume incepe cu majuscula. De
exemplu Calcul Integer va fi, intr-un interpreter, tipul calculelor care
se termina cu rezultat intreg.

Rezumfind: Cand scriu:

0 introducere in Haskell 98 prin exemple - 34 - Dan Popa


suma I n teger -> Integer -> Integer

voi intelege ca suma are tipul Integer -> Integer -> Integer
apoi Ia randul ei sum a 7 are tipul Integer -> Integer
iar expresia suma 8 7 are tipul Integer

Antlcipand: va spun cA veti intalni simbolul -> ~i in a~a-zisele


"lambda expresii". Acestea sunt un tel de functii ,anonime, tara nume,
care se folosesc deobicei numai local, acolo unde este nevoie o
singura data de ele.

0 introducere fn Haskell 98 prin exemple - 35 - Dan Popa


Pe urmele functiilor care manipuleaza functii

Despre: Doua exemple de funcfii care manipuleaza alte functii:


operatorul pentru calculul sumelor ~ $i functia care ap/ica o alta
functie de doua ori Ia rfmd. Manipularea repetata a functiilor.
Recursivitatea. Factorlalul $i func(ia lui Ackerman-Peter. Pattern-uri
$i pattern-uri cu garda. Sign - semnul unui numar . ~ $i factorialul
refacute. Despre o suma de zerouri sau despre lambda expresii $i
despre $ablonul pentru orice ( _ ). Cum putem folosi lambda
expreslile

2.1. Functii care manipuleaza functii : Sumarea;

7J.n exemplu tipic de functie care manipuleaza alte functii este

operatorul scris de obicei cu litera greceasca sigma majuscula folosit


Ia notarea sumelor, ~ . in realitate, cand exprimam matematic, de
exemplu, " suma de Ia i egal cu min Ia max din f(i)" foloslm practlc functla
sigma ( ~ ) cu trei argumente: min, max ~i f. 0 asemenea functie de 3

0 introducere in Haskell 98 prin exemple - 36 - Dan Popa


argumente vom implementa in continuare in Haskell 98.

Oat fiind acest deziderat suntem obligati sa ne punem o prima intrebare:


Cum vom manipula al treilea argument, functia f ? Se ~tie ca in limbajele
de programare care permit scrierea unei asemenea functii este nevoie de
precautii deosebite, de exemplu de metoda de transfer de parametri
numita de speciall~ti "call by name" ~i care nici nu este implementata in
unele limbaje (cazullimbajului Pascal). Cum rezolva Haskell 98 problema
(care in alte limbaje nu este deloc triviala) ?

Raspunsul este simplu. Haskell 98 este un limbaj functional, care


manipuleaza cu aceea$i naturalete argumente simple (ca intregii) $1
argumente construite cu constructorul ->, cum sunt functiile. Nici aplicarea
unui parametru altuia nu pune problema. Daca g este o functie care are
parametrii x ~I y iar x este o functie $1 se respecta regulile de "potrivire"
(expertii vor citi "compatibilitate") a tipurilor, atunci prin x y se va intelege x
care prime~te ca parametru pe y. Practic, gand~i-va ca in Haskell 98,
atunci cand scrieti pe f(x) sub forma (mai puternical) f x, este posibil ca
atat f cat ~; x sa fie ni~te variabile II

Sa rulam pentru inoeput pe calculator, un exemplu ceva mai simplu decat


sigma: o banala functie g care o aplica pe alta f, de doua ori succesiv
asupra aceluia$i argument. Functia a doua, f, va fi primita ca parametru.

g ( I nteger -> Integer ) -> Integer -> Integer


g f X = f ( f X

Cap2Par1 Ex1.hs

0 introducere in Haskell 98 prin exemple - 37 - Dan Popa


Ce calcule se vor face cand evaluati g f 10 ? intai se va aplica f lui
10 $i se va obtine o valoare. Aceasta valoare va servi ca argument
pentru f-ul exterior.
Pentru a calcula de exemplu g f 10 avem nevoie $i de o functie
anume f pe care sa o aplicam de doua ori unui argument. Vom folosi
vechea noastra cuno$tinta, functia f care incrementeaza un numar.
Faptul ca primul argument al lui g se nume$te tot f este o pura
coincidenta, II puteti nota $i cu h, daca doriti, ceea ce nu schimba cu
nimic lucrurile.
g (Integer -> Integer) -> I nteger -> Integer
g h X =h ( h x )

in final , programul complet ar putea fi a$a:


~-~ Ca ZParl Ex1 hs - Advanced Ed1tor
file ~dlt .a.ookmaru Iools ~ettl ngs J:ielp

Cl 5 ~ 0 ~ ~ ~ X ~ ~ ~~~
-- Funct i a g aplica o alta functie de doua ori ..
r-
-- Dan Pope 30 •artie 200~

f ..
. . Integer -> Integer
f X = X+ 1
r-
g .. <Integer -> Integer) -> Integer -> Integer r;
g f X = f (f x ) r-
~
line· 2 Col 27 I INS I I
Programul Cap2Par1Ex1 .hs in curs de editare.

lar executia acestui program va decurge a$a:


Main > g f 10 (expresia data interpretorului)
12 (raspunsul acestuia, adica 10 incrementat de doua
ori)

0 introducere In Haskell 98 prin example - 38 - Dan Popa


Acesta a fost un prim exemplu de functie care manipuleaza o alta functie,
primind-o ca pe un argument. La operatorul sigma vom reveni In alt
subcapitol, dupa ce vom studia modul de realizare al calculelor repetitive.

2.2. Manipularea repetata a funcfiilor: Recursivitatea; Exemple:


Factorialul $i functia lui Ackerman-Peter

in practic~. deseori este nevoie ca in mod repetat, {de exemplu


repetat de un num~r $tiut de ori) s~ se fac~ anumite operatii. Sumele
calculate cu sigma sunt un asemenea exemplu. Un exemplu inc~ $i
mai simplu este functia factorial , data de formula: fact(n) = n * {n-1)
* ... • {n-k) * ... * 2 * 1. Factorialul lui n este deci produsul tuturor
numerelor de Ia 1 Ia n.
in limbajele functionale, cum este Haskell, asemenea calcule se
implementeaz~ cu ajutorul recursivit~tii. Exist~ $i alte metode, de
exemplu factorialul s-ar putea implementa $i ca produs al
elementelor unei liste {exercitiu bun de f~cut dup~ ce studiati listele)
dar tot Ia recursivitate se recurge pan~ Ia urma.
Veti avea nevoie de o definitie recursiv~ a factorialului, adic~ de o
definitie a lui factorial de n functie de factorialul lui n-1 . Folosim
definitia clasic~ din matemati c~: fact(n) = n * fact{n-1) iar pentru eel
mai mic n, n=O, fact{O) = 1. Matematicienii scriu astfel de functii
folosind de obicei o acolad~ .
Din punct de vedere al teoriei calculabiiMtii, informal vorbind,
puterea recursivitatii este sim i lar~ cu cea a buclelor din limbajele

0 introducere in Haskell 98 prin exemple - 39 - Dan Popa


imperative $i a functiilor fold din limbajele functionale. Ceea ce se
poate implements in aceste limbaje cu ajutorul buclelor se poate
implements in Haskell folosind recursivitatea (sau fo/d-urile).
Pentru a implements functii cu acolada in Haskell trebuie sa
specificati pentru fiecare varianta din acolada formula de calcul $i
argumentele. Sistemul Haskell va face rand pe rand (succesiv, in
ordinea data) incercari (numite in engleza "potriviri de $Sbloane" -
"pattern matching") $i va aplica argumentelor formula din cazul
potrivit.
fac t : : Integer-> I nteger
fac t 0 1
fact n = n * (fact (n-1))
Cap2Par2Ex1 .hs

: : Intege r -> rntege r


=1
=n • (fact (n - 1) )

Factorialul - programul Cap2Par2Ex1.hs Tn curs de edltare

Retineti $i faptul ca pattern matching-ul functioneaza $i cu perechi,


liste sau alte tipuri construite cu constructori de tip (de exemplu
arbori). Procesul de pattern matching poate patrunde in structura
unor asemenea date compuse $i izola componente care sa fie apoi

0 introducere fn Haskell 98 prin exemple - 40 - Dan Papa


folosite in calcul. in aceastA priviintA Haskell seamAnA cu limbaje
mai vechi cum sunt Prolog ~i ML. lar argumentele unei functii sunt
adesea (in Haskell) fie perechi, fie liste, fie perechi de liste ori liste
de perechi ori alte structuri compuse .
Atentie: Paranteza apelului unei functii f(x) se pune practic in fata lui
f - iar adeseori, cand prioritatea ~i asociativitatea operatorilor permit,
nici nu se mai pune paranteza. in exemplul de mai sus am pus
argumentul n-1 in parantezA deoarece in lipsa ei s-ar fi inteles cA
doresc sA evaluez fact(n)-1 nu fact(n-1) I Ca efect al unei asemenea
gre~eli ar fi urmat o serie de apeluri recursive care ar fi umplut stiva
~i ar fi generat in final un mesaj de eroare.
Un exercitiu de acest tel dar putin mai complicat ar fi sa
implementam functia sau ~irul de functii Ackermann-Peter dat de
celebrele formulele :
Go(x) = x+1
Gn•1(Q)=Gn(1)
Gn.,(x+1 )=Gn(Gn+,(x))
in Haskell acest G devine o func~e cu doi parametri, dar numele ei
trebuie scris cu litera mica, a~a cum am mai spus. Deci programul
este: g : : Integer -> Integer -> Integer
g 0 X = X t 1
g n 0 g (n-1) 1

g n x g (n-1) (g n (x-1))
Cap2Par2Ex2.hs

Editati ~i salvati acest program cu editorul cu care ati lucrat panA


acum.

0 introducere Tn Haskell 98 prin exemple - 41 - DanPopa


~-~ Cap2Par2Ex?. h•- KWrole
~ ~dlt (iookmerkt Iools ~enlngt l::lelp

06&0~ ~ ~ XQ)~ ~ ~ •
Functia lui Ac kerMann - Peter
-- Dan Popa 30 •artie 2005

g :: Integer - > Integer - >Integer


g 0 X X + 1 =
g n 0 =
g ( n - 1) 1
g n x =
g ( n - 1) (g n (x- 1))

Una. 4 Col 38 OVA

Edltarea programulul Cap2Par2Ex2.hs

Apoi cereti interpretorului hugs sa calculeze cateva valori ale acestei


functii atat de rapid crescatoare. intre timp, urmariti cu top
incarcarea procesorului sistemului.

t. "f• • I I
. · ·r-1~
File Sessions SetUngs Help
Main> g 0 2 ...
3
Main) g 1 2
4
Main) g 1 3
5
Main> g 1 4
6
Main) g 2 4
11
Main> g 2 5
13
Main> g 3 5
253
Main> g 3 6
509
Main> g 4 6
I
D.[[t]
Calculul valorllor functlellul Ackerman-P6ter cu programul Cap2Par2Ex2.hs

Una dintre cele doua imaginile alaturate arata outputul comenzii top

0 introducere in Haskell 98 prin exemple - 42 - DanPopa


~i puteti remarca faptul ca hugs, interpretorul de limbaj Haskell,
daca este pus sa calculeze urmatoarele valori ale lui g ajunge sa
solicite toata puterea de calcul a sistemului, adica tot timpul
procesorului. in imagine, pe un AMD Duron Ia 1.2 Ghz, timpul
procesorului e ocupat in proportie de aproape 97% cu calculul
functiei g. Pe un Intel Dual Core 2.2.GHz ocupa un nucleu, 100%.
Nu va amagiti ca un procesor mai rapid va calcula mult mai multe
valori ale functiei g. Acest ~ir de G-uri, este un celebru ~ir de functii
foarte rapid crescatoare $i chiar $i ultimul procesor de pe piata va fi
ingenunchiat repede, probabil de unele din valorile urmatoare ale
argumentelor lui g sau de acela$i g 4 1.

Fllo S.IIIOftl Sdr!V• Ht p

8 :57pe ..., <42 llin. 1 user. load IIVerase: 1.62. 1.15. O.n
57 prooe5Se3: 5o4 sleep.ln6. 3 runn.ln6. 0 zo.bte. 0 st~
CPU s tates: 99.01: user. 0.91: syst.... 0.01: nice. 0.01: idle
Neto : 2557801< av. U<46281C used. 14U52K free . 01( shrd. 102601< buff
Seoap: 2570001< IIV. 01( used. 2570001< free <487561< cached

Ll"'"''
1904 den
pttwnwww,_IMQMMMUIMIII*'MIIi!MIIIII!MII'
20 0 6408 6408 1160 R 96.9 2.5 15 :23 hugs
1931 dan 10 0 1032 1032 812 R 0.3 0.4 0 :00 top
1907 den 9 0 8960 8960 7252 R 0.0 3.5 0:00 konsole

Outputul comenzil top aratA 7ncArcarea procesorului.

Oricum puteti, calcula valorile g 3 5 , g 3 6; ba chiar g 4 0 $i g 4 1


chiar tara sa aveti un PC din top.
Daca un calcul dureaza prea mult pe computerul dumneavoastra
puteti opri cu tastele CTRL-C evaluarea care pare ca nu se mai
termina. Cu aceasta experienta Tn plus revenim Ia operatorul suma
(sigma) pentru o incercare de a-1 implementa.

0 introducere in Haskell 98 prin example - 43 - Dan Popa


2.3. Operatorul •suma•- o prima fncercare nereu~ltA (deoarece
Haskell 98 nu permlte varlablle ale caror nume se repetA 1n
ace/a~i ~ablon)

incepem tentativa noastrA prin a alege nume pentru cele trei


argumente ale lui sigma. Am considerat cA, deocamdatA, sigma
calculeazA sume de Ia o valoare minimA (min) Ia o valoare maxima
(max) din valorile functiei f cu argument lntreg. Functia face deci un
calcul pe care intr-un limbaj imperativ l-am fi facut cu o bucla for.
lata o asemenea bucla scrisa in Pascal:

for i:= min to max do begin


suma :=suma + f(i)
end

Dar sa revenim Ia Haskell 98. Functia f avand semnatura (tipul)


lnteger->lnteger rezulta imediat ca sigma va avea tipul:
Integer->Integer->(Integer->Integer)->Integer
Primul argument este valoarea minimA a contorului, al doilea e
valoarea maxima, al treilea este functia pentru care se calculeaza
suma de valori, iar ultimul Integer este tipul rezultatului. Carcota$ii
vor comenta ca ultimii doi de Integer ar putea fi i nlocuiti simultan cu
alt tip, de exemplu Float. Le voi raspunde cA poate fi inlocuit $i cu un
tip oarecare (a), polimorfic dar dotat cu adunare, insa nu am ajuns Ia
acel capitol care descrie tipurile polimorfice $i nici Ia eel care descrie

0 introducere in Haskell 98 prin example - 44 - DanPopa


a scrie c~ in toate cazurile r~mase f(x,y) are valoarea 0 pot scrie
regula f _ _ =0 Ia finalullistei de reguli care descriu functia f.

2.4. Pattern-uri 'i pattern-uri cu garda; Sign - semnul unui


numar

Exemplul urm~tor implementeaz~ una din cele mai simple functii


matematice, atat de simpl~ incat rezultatul ei nu depinde de valoarea
singurului s~u argument ci doar de o proprietate a acestuia, semnul
s~u. Este functia sign, cea care ne d~ semnul unui intreg.
Matematicienii o scriu cu acolad~ . exprimand faptul ca are valoarea
1 pentru argumente pozitive, 0 exact cand argumentul s~u este 0 $i
-1 dac~ i se d~ un argument negativ. in Haskell o vom scrie ins~
a$a:

sign :: Integer-> Integer


sign x I X > 0 1
I X == 0 0
X < 0 -1

Cap2Par4Ex2.hs

0 introducere rn Haskell 98 prin exemple - 47 - Dan Popa


~-- CapZPar4Ex1 hs - KWnte
Elle fdh ~ookmarks Iools ~ettings t:!elp

D6& 0 ~ ~ ~ X~[j ~ ~~
-- Functia sign de calcul a se•nului unui njumar
-- Ilus treaza notiunea de garda intr- un sablon
-- Observati ca pentru acelasi sablon sunt posibile
-- •ai multe formule de calcul iar accesul la fiecare din
-- ele este pazit de un predicat numit garda
-- Oupa : A Gentle Introduction to Has kell 98 pg 16

sign :: Integer - > Integer


sign x I X >0 =1
I X == 0 =0
I X ( 0 = -1

41 I l•J •
Line: 7 Col: 1 I INS I I
Cap2Par4Ex2.hs -in curs de editare

Acest ~ablon are trei formule de calcul pentru valoarea rezultatului,


fiecare "pazita" de un predicat numit garda. Exemplu acesta este
preluat din volumul In limba engleza "A Gentle Introduction to
Haskell 98" scris de Paul Hudak, John Peterson ~i Joseph H. Fasel,
unde II gasiti Ia pagina 16. Dupa ce editati acest program veti avea o
mica problema Ia executie. Puteti intreba interpretorul cat este sign
0 , sign 1 ~i similar, pentru valorile pozitive. Totw?i cand veti lncerca
sa aflati ce semn are -2 va fi necesar sa-l scrieti In paranteza, altfel
obtineti o eroare. Tastati sign (-2) nu sign- 2!
Problema are legatura cu faptul ca In Haskell aplicarea functiilor
asociaza Ia stanga deci sign- 2 este interpretat ca (sign (·)) 2. Dar

0 introducere In Haskell 98 prin example - 48 - Dan Pops


clasele. Revenind Ia functie, este evident ca daca min = max functia
sigma are doar un singur termen de adunat, f(max) . Daca lnsa min <
max atunci avem de adunat f(min) cu suma f-urilor avlnd ca
argumente valorile de Ia min+1 Ia max. incercam deci sa definim
sigma astfel:
sigma Integer->Intege r->
(Integer->Intege r )
->Integer
sigma max max f f ma x
sigma min max f ( f min) + (s igma (min+l) max f )
Cap2Par3Ex2.hs- acesta e un exemplu de program gr-.it
~-- Cap2Par3E<1 hi- KWnle
(tte ~drt Uooklnw Iools serungs t!elp

Cl E!~O~ ti'} ("» ;)(Ql~ ~ ~~


-- Functia sig•a de calcul a unei su•e
-- Dan Popa 30 •artie 2005
-- pri•a tentative gresita
-- Haske! nu ad•ite intr- un sablon doua variabile identice

-- 0 fun ctie banala care va fi su•ata


f ..
. . Integer -> Integer
f X = X +1

-- Si functia sig•a
s ig•a ::Integer-> Integer - > (!nteger- >Integer) - >Integer
sig•a •ax •ax = f •ax
s ig•a .tn • ax = (f •in) + (sig•a <•in+l) •ax)

Line. 4 Col. 59 I INS.LL

Cap2Par3Ex2.hs- acesta e un exemplu de program gr..it

incercarea de a edita ~i apoi de a rula programul decurge astfel.


Din pacate, In cursul Tncarcarii sursei din fi~ier, In faza de analiza a
dependente-lor, interpretorul semnaleaza eroarea (cum, n-ati vazut-

0 introducere in Haskell 98 prin exemple - 45 - Dan Popa


o inca ?) scriind mesajul:

ERROR "Cap2Par3Ex1.hs" : 13 - Repeated variable max in pattern

Altfel spus, interpretorul nu permite ca intr-un $ablon pentru pattern


matching sa existe doua variabile cu acela$i nume. Ramane sa
cautam a exprima in alt mod faptul ca sigma min max f = f
max pentru cazul cand min=max .
Spre marea noastra U$Urare acest lucru se poate totu$i face in
Haskell dar in alt mod, folosind pattem-urile ($abloanele) cu garcia.

I • ~·~ f • I t ~ ; t • • JIDJ[IC
File Sessions Settings Help

Reading file "/usr/share/hugs /lib/Prelude.hs":


Reading file "Cap2Par3Ex1.hs ":
Oependenc~ anal~ s is
ERROR "Cap2Par3Ex1.hs ":13 - Repeated variable "max" in patte
Prelude>
Prelude>
Prelude>
lll[tj]
Cap2Par3Ex2.hs- rularea exemplulul fl mesajul de eroare

De altfel n-ar trebui sa ne miram, nici banalele functii din matematica


nu admiteau folosirea a doua variabile cu acela$i nume iar regula e
valabila $i in Haskell. Vom vedea insa ca spre deosebire de
matematica, functiile din Haskell pot avea $i argumente ignorate,
care se potrivesc Ia pattern-matching cu orice, notate cu _ $i care
pot apare repetat in cadrul aceleia$i alternative. De exemplu, pentru

0 introducere in Haskell 98 prin exemple - 46 - Dan Popa


functia noastra sign este definita pentru numere iar (-)::Integer ->
Integer este un argument de tip functie nu de tip numeric.
Deocamdata este suficient sa scrieti argumentul -2 In paranteza
pentru a evita semnalarea unei erori a$a criptice cum este cea din
figura urmatoare.
Retlnetl: Operatorii = $i == se scriu Ia tel $i au aproape aceea$i
semnificatie caIn limbajul C. AI doilea, ==, serve$te Ia comparatii I
• II ~

/usr/share/hugs/libfPrelude.hs
Cop2Par4Exl.hs
Tupe :? for help
Moin) sign 0
0
M&in) sign 1
1
Main> sign -2
ERROR - Illegal Heskell 98 class constraint ln inferr ed type
••• Expression : sign - 2
• • • Type : Hua (Integer - > Integer) => Integer -> Integer

Main) sign (-2)


-1
Main) I
D.l[fj]
ExecutJa Cap2Par4Ex2.hs - evitati mesajul de mal sus scrlind : sign ( - 2 )

2.5. Sigma ( L ) reficut

Acum il putem scrie U$Or in Haskell pe sigma, operatorul de sumare.


incepem programul cu o functie banala ale carei valori vor fi
insumate.
f Integer - > Integer
f X X + 1
Acum sa scriem functia sigma. Am inclus $i un caz In care min >
max, afirmand ca suma este zero lntr-o asemenea situatie.

0 introducere Tn Haskell 98 prin exemple - 49 - Dan Popa


sigma Integer -> Integer- >
(Integer-> I nteger)->Intege r
sigma min max f min == ma x f min
min < max ( f min) +
(s i gma (min+l ) ma x f )
min > ma x = 0
Cap2Par5Ex1 .hs

Pe ecranul editorului, respectand regulile a$ezarii bidimensionale,


programul va arata a$a:

-- Func tie s igma d e c alc u l a u n e i s u•e


-- Dan Pope 30 • artie 2005
-- a d ou o t e nta tive c orec t a
-- Ha s ke l nu ad• ite intr- un s ablon doua variabile 1de ntice
-- dar a d • ite sab loane c u gorda

-- 0 func tie b a nal e cere v a fi s u mate


f :: In t e ger - > In t eger
f X : X t 1

-- Si func tia s igma


s ig•e . . I nt ege r - > Int e ge r - > <In t e g e r - >In t e ger) - >Int eger
s ig• a •in •ax min == max = f min
• in < max = ( f min) • (sig• a < •in~l> • ax)
Min ) M 6X : 0
••
L~n• 13 Col s::fiNS..C:

Cap2Par5Ex1 .hs - in curs de edltare

Acum puteti testa: sigma 1 2 f va da rezultatul 5. Ceea ce este


corect deoarece calculeaza suma dintre f(1) $i f(2) adica dintre 2 $i
3. lar sigma 1 10 f va da rezultatul 65. Ceea ce este de asemenea
corect deoarece calculeaza suma dintre f(1 ), f(2) ... $i f(1 0) adica
sume progresiei 2,3,4,5, .... 11 . Ori eel putin acest exemplu II putetti
verifica printr-o formula clasica folosita Ia calculul sumei elementelor
unei progresii aritmetice: (2 + 11) • 10 I 2 = 65.
Cap2Par5Ex1 .hs - rulare

0 introducere In Haskell 98 prin exemple - 50 - Dan Popa


i ) • I

FUe Snt•ons Sailings Hetp

Haskell 98 • ode: Restart with co••and line option - 98 to enable extensions


Reading file "/usr/share/hugs/lib/Prelude.hs":
Reading file "Cap2Par5Ex1.hs":

Hugs session for:


/usr/share/hugs /lib/Prelude.hs
Cap2Par5Ex1.hs
Type :? for help
Main> sig•a 1 10 f
65
Main> sig•a 1 2 f
5
Main> I ..
D.lltll
in loc de 0 puteti pune $i expresia error nArgumente nepotrivite r.

2.6. Factorialul reficut

V~ propunem s~ revenim putin asupra implement~rii anterioare a


factorialului.
fac t :: Intege r-> Integer
fact 0 1
fact n = n * (fact (n- 1 ))

Cap2Par2Ex1 .hs - reluat

Ea avea o mic~ deficient~. Dac~ In locul unui argument pozitiv am fi


scris unul negativ functia nu mai reu$ea s~ calculeze rezultatul
deoarece se apela Ia infinit (eel putin teoretic). Versiunea corect~ e:

0 introducere in Haskell 98 prin exemple - 51 - Dan Popa


fact2 :: Integer-> I nteger
fac t 2 n n < 0 0
n ==0 1
n > 0 = n * ( f act2 (n-1) )

Cap2Pari)Ex1.hs

Editati $i rulati acest exemplu:

~/" .. Cap2Par6Ex1 hs - KWnte


file £dil £!_ookmarks rools ~ellings .t!elp

-- Functia fac toria refacuta cu garda


-- Dan Popa • 30 martie 2005

fact2 Integer - > Integer


fact2 n I n < 0 ~ =
I n == 0 - 1
I n >0 =n H (fact2 (n- 1))

Llne: 5 Col: 21

Cap2Pai1)Ex1.hs • in curs de edltare

Dupa lncarcarea programului de catre interpreter puteti sa


transmiteti functiei fact2 valori negative, tara teama.
Cu ocazia acestui exemplu unii cititori m-ar putea intreba de ce am
numit functia fact2. Raspunsul este ca nu pot coexista doua functii
cu acela$i nume In acela$i modul de program. Dar, este posibil sa
lncarcati succesiv doua (sau mai multe module) $i sa precizati de
fiecare data ce functii doriti sau nu doriti sa se lncarce din acel
modul. Pe scurt, programarea modulara In Haskell permite sa ocoliti
asemenea conflicte de nume, precizand ca utilizati un anumit modul

0 introducere in Haskell 98 prin exemple - 52 - Dan Popa


(colectie de tunctii) dar tara anumite functii de acolo, functii pe care
intentionati sa le rescrieti. Dar toate aceste detalii ar fi trebuit incluse
lntr-un capitol separat dedicat special programarii modulare In
Haskell. Se folose~te aici declaratia import, de ex: import Data. Char.

0 sesiune de lucru In care e testata functia fact2 e surprinsa In


imaginea de mai jos. Observati cum fac2 (-1 0) se evalueaza cu
succes in timp finit.

fa ct 2 1
f ac t 2 2
f ac t 2 3
fa c t2 4
fa c t 2 ( - 10)

Cap2Pan)Ex1.hs - o seslune de lucru.

$i in acest program In loc de 0 puteti pune ~~ o expresie care


semnaleaza eroarea: error "functia fact: Argument nepotrivit r.

2.7. Despre o sumi de zerourl sau despre lambda expresil ''

0 introducere rn Haskell 98 prin exemple - 53 - Dan Popa


despre ~blonul pentru orice ( _ )

Operatorul nostru sigma dat mai lnainte ne permite sa calculam


sume de functii care transforma Integer In Integer. (Functii cu tipul
lnteger->lnteger). Se pot pune doua problema noi aici:
a) Am putea calcula sume oarecare ?
b) Putem calcula sume de constante?
Raspunsul Ia lntrebarea a) este clar. A~a cum este definit acest
sigma, el permite sa sumam doar valorile unor functii de tipul
lnteger->lnteger.
De aici rezulta ~i raspunsul Ia intrebarea b. Constanta 0, de
exemplu, avand tipul Integer ~i nu lnteger->lnteger nu poate fi nici ea
sumata cu sigma. incercati sa propuneti interpretorului sa calculeze
sigma 1 2 0 ~i veti obtine un mesaj de eroare de tip.
'·-'!. I • • 0 ,11
File Sessions Se11ings Help

Haskell 98 ~ode: Restart with co•aand line option -98 to enable extensions·

Reading file "'/usr/share/hugs/lib/Prelude.hs'':


Reading file "'Cap2Par7Ex1.hs"':

Hugs session for:


/usr/share/hugs/lib/Prelude.hs
Cap2Par7Ex1.hs
Type :? for help
Main) sigaa 1 2 0
ERROR - Ill egal Haskell 98 class constraint in inferred type
••• Expression : sig•a 1 2 0
••• Type : Nu1 (Integer -) Int eger) => Integer

Main> I
D.llB
Mesaj de eroare de tip. Atl incercat si sumati constante nu functii.

0 alta solutie se lntrevede lnsa: lnteresant ar fi sa reu~im sa

0 introducere in Haskell 98 prin exemple - 54 - Dan Popa


i'nlocuim constantele 0, 1 , 2 cu fun~ii constante. Obtinem astfel
chiar doua variante posibile ale programului.

Prima varianta este inspirata de elemente fundamentale din teoria


lambda-calculului. Conform cu aceasta teorie, abstractia care
asociaza orice x cu valoarea n se noteaza cu: ( Ax . n) Unei
asemenea functii anonime i se mai spune, Ia modul general,
A - expresie, din cauza acestei notatii care i'ncepe cu litera A .
In Haskell ea se va scrie putin diferit, deoarece litera greceasca
lambda nu exista in vocabularul limbajului. Transcrierea ei este de
forma (\ x -> n). Aici ajungAnd nu pot sa nu remarc faptul ca pe
ecran, combinatia (\ aduce putin cu litera greceasca lambda ( A ).
Ca urmare, putem sa definim o functie care transforma un intreg in
functia anonlmfJ constanta care ne da acea valoare :

dan Integer -> Integer -> Integer


dan n ( \x -> n)

Cap2Par7Ex1.hs

Pentru fiecare valoare intreaga (2,3,7,10 ... ) functia dan ne da ca


rezultat una din functiile anonime (lambda expresii): ( \ x - > 2) ,

(\x -> 3) , (\x -> 7) (\x -> 10) .lar acestea sunt functii
constante dar de tipul lnteger->lnteger ~i pot fi i'nsumate cu sigma.

In general, In Haskell 98, prin lambda expresii se definesc functii

0 introducere In Haskell 98 prin exemple - 55 - Dan Popa


anonime, de una sau mai multe variabile, scriind Ia inceput "lambda"
(adic~ \ ) apoi variabilele libere (de care depinde rezultatul functiei) ~i
in final (dar separata de variabile printr-un simbol care in Haskell
este "->") expresia cared~ valoarea rezultatului. La fel ca ~i in teoria
matematic~ a lambda-calculului, aceste functii anonime pot fi folosite
foarte bine in locul unor functii nominale (cu nume). Li se pot da
argumente sau pot fi transmise ca parametri.

Retineti: Lambda expresia este practic o functie anonlma


(numiti 'i abstractie) care nu-i dati prin nume ci printr-o
descriere a argumentelor 'i a expresiei rezultatului.

Exemple de utilizare ale unor lambda-expresii:

(\X-> X+ 1) 1 da rezultatul 2
(\ X y -> X + y) 23 d~ rezultatul 5
(\ g x -> g x) f 1 d~ rezultatul 2, f fiind pt. noi incrementarea

Alt~ variant~ de a calcula cu sigma sumele de constante: S~ definim:

dan2 Integer -> (Integer -> I nteger )


dan2 n n
Cap2Par7Ex2.hs

Transfonnarea constantel in funClfle constanti : Cap2Par7Ex2.hs

0 introducere in Haskell 98 prin exemple - 56 - DanPopa


.. ·. I!]a';!
Ed• fO! llool4arls Iools ~elling~ tfelp

Do& Oc:9 t11 <"' X Ql r;D ~ ~ ~


-- Functia sig• a de cal cul a unei su• e .
- Dan Popa 30 • ar tie 2005
-- a doua tenlaliva corecta
-- Has ke! nu adllite intr- un sablon doua variablle ldentice
dar ad•ile sabloane cu garda

-- Transfor•area cons t anlel in funcl ie

dan ::Integer - > (Integer -> Integer)


dan n = !\x - > n~

dan2 ::Integer - > ( I nt eger - > I nteger)


dan2 n _ = n

4 ..< 4 •
...
lint. 10 Cot 18 INS • -

Ce valoare va avea dan2 100 ? Evident, valoarea va fi o functie de Ia


l ntregi Ia intregi. Deoarece dan2 n _ = n deducem ca dan2 poate fi
l nteleasa $i ca o functie de doua argumente care da in final un
intreg. 0 asemenea functie, daca i se transmite doar valoarea
primului parametru da ca rezultat o functie care va lega urmatorul
parametru de rezultatul final.
Cu ambele functii putem scrie acum sume de constante ci operatorul
sigma:
> sigma 1 3 (dan 1)
3
> sigma 1 2 (dan2 0)
0
incercati sa dati alt nume decat "dan" functiei care transforma o
COnstanta intr-O funcfie COnstanta. Sa-i zicem "makefconst" ? in
imaginea de mai jos se vede ca un nume ca "dan2" este neintuitiv in

0 introducere in Haskell 98 prin exemple - 57 - Dan Popa


practica. Dati functiilor dvs. nume intuitive I

f ~ • l • I I : '. I • • • t 1 t :0 I •

File Sessions Setllngs Help

Main> sigNa 1 2 (dan 0)


..
0
Main> sigNa 1 2 (dan 1)
2
Main> sigNa 1 3 (dan 1>
3
Main) dan2 0 1
0
Main> dan2 0 2
0
Main> dan2 1 89
1
Main> sigma 1 2 (dan2 0)
0
Main> sigNa 1 2 (dan2 O>l ..

D.lfll
Operatorul sigma pus sa calculeze o suma de n constante egale.

Observatie: De$i o suma de n constante egale se calcula imediat


printr-o lnmultire nu putem sa nu remarcam abilitatea limbajului
Haskell ($i a alter limbaje functionale) de a face calcule In care
transforma valorile In functii, functiile in functii, functiile in valori ...
Aceasta calitate o au toate limbajele functionale: aceea de a
manipula cu aceea$i naturalete functiile ca $i valorile. Din acest
motiv nici nu exista declaratii de constante In Haskell. Putem totu$i $i
este suficient sa definim o functie tara parametri, cu numele
respectiv $i care ne da valoarea dorita:
no ta max In t eger
not ama x 10
Cap2Par7Ex3.hs

0 introducere in Haskell 98 prin example - 58 - DanPopa


2.8. Cum putem folosi lambda expresiile

Lambda expresiile sunt functii anonime care pot fi folosite atAt ca


ni~te functii cat ~i ca argumente. De altfel delimitarea strict~ care
exist~ in limbajele imperative intra functii ~i argumentele lor nu mai
exist~ intr-un limbaj functional cum este Haskell.

FUI S..llOnJ Sdngt Help

0
Main> (\x - > x • 1> 1
2
Main> (\x y - > x • y ) 2 3
5
Main) (\g X -> g x) r 1
2
Mein >
Main> slg• a 1 4 (\x - > 2>
8
Main ) s l g• a 1 4 (\x -> x • x)
30
Main) (\x - > x • x) S
25
Mai n) I
D.lin
Exemplele de utlllzare a abstractlllor (functli anonlme) .

Coment~m unele dintre exemplele practice de mai sus:


>sigma 1 4 (\ x -> 2) calculeaz~ suma cu patru termeni (de Ia 1 Ia
4) fiecare din ei fiind valoarea functiei constante egale totdeauna cu
2. Obtine ca rezultat 8.
> sigma 1 4 (\ x -> x * x) calculeaz~ suma primelor patru p~trate

perfecte. Functia care d~ termenii sumei este o functie anonim~ dat~

printr-o abstractie. Suma care rezuM este 1+4+9+16 = 30.


Aceea$i lambda expresie poate servi Ia a ridica un num~r Ia p~trat,

comportandu-se exact ca o functie care are nume. (\ x -> x * x) 5 va


da ca rezultat 5*5 adica 25.

0 introducere fn Haskell 98 prin exemple - 59 - Dan Popa


Retineti din acest subcapitol:
- Cum se scriu In Haskell functiile anonime ca lambda expresii.
- Faptul c~ ele se pot folosi atat pe post de functii cat ~i pe post de
argumente ale functiilor. Acest lucru era de a~teptat de Ia un limbaj
functional.
- C~ exist~ acel "_" care folosit lntr-un ~ablon se potrive~te cu orice.
Semnificatia lui este eel valoarea acelui parametru poate fi oricare.
- Nu uitati cum se scriu definitiile care folosesc pattern matching-ul
cu gardel (g~rzi). Din nefericire Ins~ lambda expresii cu gard~ nu se
pot scrie direct In Haskell dar vom vedea c~ se pot totu~i scrie
lambda expresii cu case-uri ~i if-uri.

2.9. Lambda expresiile cu parametrl multipli

F~rel a v~ plictisi cu multe detalii, vel spunem ~ aceste functii


anonime, - lambda expresiile - cum sunt cele de mai sus au ~i ni~te

forme mai generale, mai complexe, cu mai multi parametri, fie


prezentati succesiv (eng. curried) fie ca n-uplu (eng. uncuried).
evaluati:
Hugs> (\ a b -> a * b) 2 3

incercati, spre comparatie ~i varianta cu o pereche de parametri:


Hugs> (\ (a , b ) -> a * b) (2 , 3 )
6

~i surpriz~. admite parametrii prezentati ~I sub form~ de list~:

0 introducere fn Haskell 98 prin exemple - 60 - Dan Popa


Hugs> (\ [a,b] - >a* b ) [2,3]
6
Dar In acest caz nu-i puteti oferi ca o pereche:
Hugs> (\ [a , b] ->a* b) (2 , 3)
ERROR - Type error in application
*** Expression (\[a , b] -> a * b) (2, 3)
*** Term (2 , 3)
*** Type (b , c)
*** Does not match [a]
Se pot prezenta parametrii $i prin expresii cu structuri mai complexe,
urmand ca sistemul Haskell s~ fac~ o elaborat~ potrivire de
$abloane ca s~-i separe $i s~-i foloseasc~. lat~ un exemplu In care
sunt oferiti functiei a b $i c, componentele unei structuri care este o
pereche dintre o valoare $i o lista de dou~ valori:
Hugs> (\ (a , [b , c] ) ->a+ b +c) (1 , [2 , 3])

6
~i iar~$ i, vedem c~ functia anonim~ 1$i extrage corect valorile
necesare din structura care corespunde (trebuie s~ corespund~) . cu
ceea ce este scris In paranteza ei.

0 introducere in Haske/198 prin exemple- 61 - Dan Pops


Cat mai multe despre tipuri $i crearea lor

3.1. Un vechi exemplu: Triunghiul. Declara1ia data

Cuvantul triunghi are mai multe lntelesuri, eel putin In vorbirea


curenta. Pentru eel care consemneaza un rationament geometric,
triunghiul este un triplet de litere (ex: ABC) din figura In discutie. Tot
In plan, dar pentru eel care lucreaza cu mijloacele geometriei
analitice, un triunghi este desemnat de trei perechi de coordonate. in
geometria analitica a spatiului cu trei dimensiuni triunghiul este dat
de trei trlplete de coordonate. Triunghiul mai poate fi un triunghi
amoros adica un triplet de nume de persoane (practic notate prin trei
strlnguri cum ar fi "lon", ''Popescu", "loana") a$a cum este in unele
romane politiste.
Pentru a introduce In programele Haskell acest tip cu toate
acceptiunile lui simultane trebuie sa putem numi pe de o parte tipul
compus trlunghi generat de alte trei tipuri identice (matematicienii 1-
ar numi produsul cartezian) cat $i trip/eta de valori care va forma o
valoare a noului tip.

0 introducere in Haskell 98 prin exemple - 62 - DanPopa


Declaratiile acestea se fac in Haskell folosind cuv~ntul data, cu care
se introduc at~t constructorul de tipuri "Triunghi" c~t $i constructorul
de valori "Tripleta". Ambii constructori se scriu cu majuscule. Tipul
comun al celor trei elemente care formeaza o tripleta a tipului
triunghi poate fi un tip oarecare, il notam printr-o variabila de tip, fie
ea "a". Astfel vom putea declara folosi cu aceea$i declaratie fele de
tel de triunghiuri. Declaratia este:
data Triu ngh i a = Tripleta a a a
Cap3Par1 Ex1 .hs

in urma declaratiei de mai sus tipul triunghi care rezulta este un tip
polimorfic, put~nd exista in programul care-1 folose$te fel de tel de
triunghiuri. De exemplu:
Tripleta 'A' ' B' I C I este un triunghi de caractere,
din tipul Triunghi Char
Tripleta (1 .2,2.3) (1 .5,2.7) (1 .9,3.0) este un triunghi de perechi de
numere reale
din tipul Triunghi (Float , Float)
Tripleta "lon" "Popescu" "loana" este un triunghi de nume
(stringuri) care apartine
tipului Triunghi String
Observati ca prin inlocuirea variabilei de tip a cu un tip concret ,
Char, String,(Fioat , Float) obtinem din tipul eel mai general al
triunghiurilor tipuri particulare. Aceasta este o proprietate a
sistemului de tipuri din Haskell, unei entitati i se pot asocia mai
multe tipuri dintre care unul este eel mai general tip al ei $i este unic.
bine determinat.

0 introducere in Haskell 98 prin exemple - 63 - DanPopa


Retineti: Constructorii de tipuri ca Triunghi ~i constructorii de date ca
Tripleta se scriu in Haskell cu majuscule pentru a-i deosebi de
functiile care se scriu cu minuscule.
S~ trecem Ia partea practic~. Se pot scrie imediat functiile care
selecteaz~ dintr-un triunghi unul sau altul dintre elemente:
primu l :: Triu nghi a -> a
primu l (Trip leta m n p ) = m
Cap3Par1 Ex1 .hs

$i similar se scriu functiile aiDoilea ~i aiTreilea, care extrag din


Tripleta elementul al doilea ~i respectiv al treilea.
. ·. {!](g:~
file fd~ &ookmms rools ~erungs l:::!elp

0 5 & 0~ !11 ~ X Q) ~ ~ ~~
-- Un tip nou de date. tipul poli•orfic triunghi ~
-- Dan Popa 25 •artie 2005
-- Exe•plu din cap 3

data Triunghi a = Tripleta a a 8

pri•ul .. Tri unghi a -> 8


pri•ul (lripleta • n p) =•
8l0oile8 .. Triunghi a -> a
8l0oilea <Tripleta • n p) =n
1-
81Treile8 .. Triunghi 8 -> a
8lTreile8 (Triplets • n p) p = ..
f:;
1-
......
Line 3 Col 21 I INS I I

Cap3Par1Ex1.hs- Tn curs de a fledltat.

Ca ~~ tipul triunghi, aceste functii sunt polimorfice a~a c~ are sens sa


puneti interpretorul s~ calculeze valoarea lor pentru mai multe tipuri
(concrete) de argumente. Aceste functii vor extrage elementul dorit

0 introducers in Haskell 98 prin exemple - 64 - Dan Popa


din orice fel de triplete. Evaluati:
> p rimul (Tripleta (1.5 ,3 . 0 ) (1.4 , 2 . 0 ) (7 , 9 )
(1 . 5 , 3 . 0 )
Am extras primul element dintr-o tripleta de puncte din spatiul
bidimensionaL
>primu1 ( Trip1eta [1 , 2 , 3] [10 , 20 , 30] [40 , 50 , 60] )
[1 , 2 ,3 )
Am extras primul element dintr-o tripleta de liste de intregi.
>a 1 Do i 1 e a (Trip 1 eta ( 1 a 1
,
1
b 1
) (
1
c 1
,
1
d 1
) (
1
e 1
,
1
g 1
)

(
1
c ', 1 d 1
)

Am extras at doilea element dintr-o tripleta de perechi de caractere.


incercfmd sa evaluati:
>a1Trei1ea (Tr ipleta (\ x->x) (\y->y+l) (\z - >z+2 ) )
obtineti cunoscutul mesaj de eroare care ne spune ca interpretorul
nu poate afi$a rezultatul evaluarii, de$i extrage functia din tripleta.
t • fi' '"' I • I I .:. f • ~ • • liD X
File Sessions Settings Help

Main} pri•ul (lri ple ta (1.5 • 3 . 0) (1 . 4 2 . 0) (7 • 9) )


..
(1.5.3.0)
Main> pri•ul (Tripleta [1. 2 .3] [10. 20. 30] [40. 50.60] )
[1.2.3]
Main> alDoilea (lriple ta (' a ' .'b') (' c '. 'd') (' e ' .'g')
(' c '.'d ' )
Main> alTreilea <Tri pleta (\x- >x> (\~ - >~+1) (\z- ) z+2))
ERROR - Cannot find "s how" function for:
""" Expression : a lTreilea (Tripleta (\x - > x) (\~ - > u +
1) (\z -> z + 2))
""" Of t~pe : Integer - > Integer

oe,l extrage functJa din trlpleti1 Haskell-ul nu o poate atifa.

Citind mesajul constatam ca interpretorul Haskell 98 a stabilit corect

0 introducere in Haskell 98 prin example - 65 - Dan Popa


ca expresia data are tipul lnteger->lnteger dar nu are o functie
"show" pentru a afi$a asemenea valori. in schimb puteti lncerca sa
evaluati:
>alTreilea (Tripleta (\x -> x) (\y -> y+1) (\z -> z+2 )
) 19
21

~i obtineti raspunsul corect I lnterpretorul a aplicat a treia functie din


tripleta argumentului dat, 19. Acela$i rezultat 1-ati fi obtinut evaluind:

> (\z -> z+2) 19


21

Supliment: Pentru Haskell functiile $i constructorii de date sunt cam


acela$i lucru. Practic un constructor de date este o functie care
construie$te data compusa, dar ramane o functie. Prin urmare ei se
pot folosi In compuneri ca $i functiile.

Exemplu: AI treilea din a treia dintr-o tripleta de triplete.

> alTreilea ( alTreilea (Tripleta (Tripleta ' a'


'a ' 'a' ) (Tripleta 'b' 'c' ' d ' ) (Tripleta 'x' 'y'
' Z I) )

Rezultatul va fi: • z •

Compunerl de constructori . AI treilea din a treia dintr-o trlpleti de trlplete.

0 introducere fn Haskell 98 prin exemple - 66 - Dan Popa


Main>
Main> allreilea (allreilea (lripleta (lripleta 'a' 'a'
<Tripleta 'b' 'c' 'd' ) <Tripleta 'x' 'y' 'z')) )
Iz'
Main> I

Retineti: Constructorii de tipuri pot fi [ ] I (,) , -> dar $i constructori


definifi de utilizator dati printr-un cuv§nt scris cu majuscula.
Pentru fiecare tip astfel construit - care poate fi ~i polimorfic -
trebuie sa indicati macar un constructor de valori, numit constructor
de date, a~a cum era Tripleta pentru Triunghi. (in urmatorul
subcapitol vom vedea ca pot fi chiar mai multi constructori de date,
daca tipul dorit este o reuniune de tipuri.)
Constructorul de date nu este obligat sa foloseasca aceea~i notatie,
acela~i nume ca ~i constructorul de tip asociat lui. in cazul tipurilor
lista, perche, functie, constructorul de date ~i eel de tip au aceea~i

notatie, respectiv: [ ] , (,) , -> Am fi putut declara tipul Triunghi cu


constructor de date numit tot Triunghi in loc de Tripleta ~i exemplele
anterioare ar fi functionat perfect. Haskell deduce dupa contextul
folosirii daca este vorba de un constructor de tip sau de unul de
date. De exemplu in semnatura unei functii (care este tipul ei) ceea
ce apare este constructorul de tip. lar pe r~ndurile urmatoare, unde
este descris procesul de calcul al valorilor apar constructorii de date.

0 introducere in Haskell 98 prin example - 67 - Dan Popa


Notati $i faptul ca In cazul tipului Triunghi constructorul de date
Tripleta se comporta ca o functie cu semnatura:
Tripleta :: a-> a -> a-> Triunghi a
Motiv pentru care o data din acest tip se va scrie (Tripleta ... ......)
cu trei argumente din acela$i tip.

3.2. Tlpuri utilizator 'i reuniunea

Tipurile reuniune sunt In esenta tipuri utilizator dotate cu mai multi


constructori de date. Pot exista mai multe feluri de date, fiecare cu
constructorul ei $i ele sunt toate incluse In acela$i tip. Constructorii
pot fi lnsotiti sau nu de variabile de tip. Vom vedea (studiind
exemplul cu arborii) ca tipurile astfel construite pot fi $i recursive. Dar
sa lncepem cu ni$te tipuri nerecursive:

data Vreme Buna Rea


data Ziua Luni Marti I Miercur i I Joi I
Vineri I Sambata I Dumini ca

Atentie cand treceti cu scrierea pe randul urmator. Aveti grija sa nu


scrieti mai din stanga decat pe precedentul, In cadrul aceleia$i
structuri sintactice. Ar avea efectul unei lnchideri de structura.
De altfel tipul Bool, este definit (afirma Paul Hudak, John Peterson
$i Joseph H. Fasel In "A Gentle Introduction to Haskell98" ) a$a:

0 introducers in Haskell 98 prin exemple - 68 - Dan Pops


data Bool True I False

Pentru acest tip, Bool, Haskell 98 are o functie "show" in interpreter,


deci putem afi$a fiecare din aceste valori atunci cand o expresie se
evalueaz~ Ia a$a ceva:

> True
True

Not~ : Avansatii in lucrul cu Haskell 98 $tiu s~ declare un tip de date


nou astfel incat sistemul s~ creeze automat $i functiile "show"
necesare noului tip. Acest mod de a le declara nu este prezentat in
acest subcapitol.
Pentru tipul nostru, Vreme, vom recurge Ia scrierea unei functii
separate pentru afi$are. Micul nostru program ar putea ar~ta a$a:

data Vreme Buna I Rea

schimbaVremea Vreme -> Vreme


schimbaVremea Buna Rea
schimbaVremea Rea Buna

arataMi : : Vreme -> String


arataMi Buna "Buna"
arataMi Rea "Rea"
Cap3Par2Ex2.hs

0 introducere In Haskell 98 prin example - 69 - Dan Popa


~i ar rula caIn imaginea de mai jos:

Fie Sesswns Sewgs Help


Main) arataMi (schiabaVr eaea Buna)
"Rea"
Main> arataMi (schiabaVreaea Rea)
"Buna"
Main> I

ll!fll
Cap3Par2Ex2.hs - procesarea datelor construlte de utlllzator.

Asemanator arata $i exemplul cu zilele saptam~n ii. Aici sunt ceva


mai multi constructori de date, fara parametri, c~te unul pentru
fiecare zi a saptam~nii. Ei se comporta, intuitiv vorbind, ca ni$te
constante. Puteti sa vi-i imaginati pe constructorii zero-ari ca atare.
• a •
Ele Ed4 J!ocoboort.o looll ~"9' ~P

D6& 0 c:9 ~ (>! )( ~ ~~~~


-- Tipuri utiliza tor cu aai aulti construc tor! de da t e A
-- din Cap3 Par2
-- Dan Pope 30 aertie 2005

data Ziua = Luni I Hartl I Miercurl I Jot I Vineri


I Saa bata I Oua 1n1ca

aline :: Ziua -> Zlua


aline Luni = Mar ti
aline Marti = Mlercuri
aline Mi ercuri = Joi
aline Joi = Viner!
aUne Vineri = Saabata
aiine Seabata = Ouainica
a line Ouainlca = Luni

arataai :: Zlua - >String


ara t aai Luni ;: "Luni"

aral8a1
arataai
Marti
Hiercuri
=
"Hartl"
=
"Miarcuri"
arataai Jo i = "Jot••
arataai Viner! = "V.inerl"
ar ataai Saaba t a = "Saabat a•·
arataai
'UnO 24 co1
Duainica = "Ouainica··
JU-fiii'fs·rr--
..
Cap3Par2Ex3.hs

Functia "aratami" (scrisa tara "-") va converti numele zilei Ia tipul

0 introducere in Haskell 98 prin example - 70 - Dan Popa


String in vederea afi$~rii. 0 alt~ variant~ ar fi s~ scrieti o asemenea
functie folosind pattern-uri cu gard~. lat~ $i interpretorul Haskell
r~spunzand Ia intreb~ri despre zilele s~pt~manii :

Cap3Par2Ex3.hs
Type :? for help
Main> arata• i <• line Miercuri)
"J ot "
Main> I
[ll~

Cap3Par2Ex3.hs- in curs de rulare

3.3. Tipuri recursive 'i arbori polimorfi

Acest subcapitol discut~ un exemplu preluat din volumul in limba


englez~ "A Gentle Introduction to Haskell 98" scris de Paul Hudak,
John Peterson $i Joseph H. Fasel. Pentru cititorii romani
necunoscatori ai limbii engleze (dar oare exista informaticieni care
sa nu $tie limba englez~ ?) precizam c~ Tree inseamna Arbore, Leaf
se traduce prin Frunz~ iar Branch prin Ramur~ de$i in contextul din
exemplu 1-a$ traduce mai curand prin Ramificatie. Autori volumul
amintit declar~ tipul Arbore astfel:

d ata Tree a Leaf a I Branch (Tree a ) (Tree a )

Ceea ce define$te un arbore de "a"-uri (cu etichete din tipul a) ca


fiind sau o combinatie dintre constructorul Leaf $i un element de tip a

0 introducere in Haskell 98 prin example - 71 - DanPopa


sau o combinatie dintre constructorul Branch ~i doi (sub)arbori de
tipul (Tree a). Observati ca dupa constructorul de date se poate scrie
unul (sau mai multe) tipuri variabile (polimorfice). Noutatea este ca
acestea pot fi nu numai variabile de tip dar $i tipuri compuse
polimorfice deci construcfii sintactice care con(in constructori de tip.
Pe romane~te exemplul de mai sus s-ar scrie:
data Arbore a =Frunza a I
Ramificatie (Arbore a) (Arbore a )
0 varianta putin mai complexa a acestei declaratii are avantajul de
a determine interpretorul sa afi~eze ~i valorile din tipul nou creat.
Cum faceti ? Adaugati Ia sfar~it: "deriving Show" I lata ca ati aflat.

data Arbore a=Frunza a


I Ramificatie (Arbore a) (Arbore a)
deriving Show
Constructorii de date definiti in modul de mai sus actioneaza astfel:
Ramificatie :: Arbore a-> Arbore a -> Arbore A
Frunza :: a -> Arbore a
Sa prelucram un arbore I Scrieti programul urmator:
data Arbore a Frunza a
Ramificatie (Arbore a) (Arbore a)

tunde : : Arbore a -> [a]


tunde (Frunza a) = [a]
tun de (Rami fica tie raml ram2 ) =tunde raml ++ tunde
ram2

0 introducers fn Haskell 98 prin exemple - 72 - Dan Popa


~-~ CapJParJE"l h'- KWr~te
file ~d1t Jlookmaru Iools ~ettJngs J:!elp

CH5 lil o c9 ~ ~ X f:b~ ~ ~~


-- Tipuri utilizator cu •ai multi c ons truc tor! de date
-- din Cap3 Par3
-- Da n Pope 30 martie 2005

da t a Arbore a =Frunza a I
Ramificatie <Arbore a ) (Arbore a)

- - Func tia "tunde" va scoate din arbore lis t a


-- i nformatiilor din frunzele s ale

tunde -- [a]
Arbore a - > (a]
tunde <Frunza e)
tunde <Ra•iflcatie ra•l raa2)
=
= tunde raml ++ tunde r a a2

.._Linet3ColtO ( INSI I

C~iteva explicatii sunt necesare:


1) Cuvfmtul Arbore este constructorul de tip $i apare acolo unde
sintaxa permite sa apara un tip. Un anume arbore concret este o
data, deci va fi construit cu constructorul de date. Un arbore cum
este eel de mai jos, cu etichete intregi in frunze, deci de tipul Arbore
Integer se va scrie a$a: (Ramificatie (Frunza 3) (Frunza 4)
~i oferindu-1 ca argument funC1iei "tunde" veti primi ca raspuns lista
frunzelor din arbore:
.......
File SessiOns Settngs Help

Cep3Par3Exl.hs
Type :? f or he lp
He tn > t unde (Ra•i fi catie (fr un za 3 > ( f ru nza 4))
[3 .4]
Ha ln) I
D.[~
La executie otJtlnetf ca rispuns llsta frunzelor.

2) (a] este notatie atat pentru tipul campus "lista de elemente de un

0 introducere fn Haskell 98 prin exemple - 73 - Dan Popa


l

tip oarecare a" cAt $i pentru constructorul de date [ ]. in acest din


urma caz [a] inseamna "lista cu elementul al carui valoare e data de
variabila a". Valoarea acestui a este stabilita de pattern matching.
Deci randurl urmator din program trebuie inteles a$a cum e explicat
mai departe.

tunde :: Arbore a-> [a]

Functia "tunde" prime$te un argument de tipul "Arbore cu etichete de


un tip a-oarecare" $i returneaza o lista de elemente de acel tip a-
oarecare, acela~i tip de elemente ca al etichetelor din frunzele
arborelui dat. Toate frunzele arborelui au ca etichete valori de
acela$i tip "a" $i bineinteles toate elementele listei rezultate sunt tot
de tip "a".
Haskell este, ati observat deja, un limbaj tipizat, cu toate
consecintele care decurg de aici. Unei expresii i se calculeaza nu
numai valoarea dar $i tipul. Ca urmare a procesului de aflare a
tipului, fiecare expresie capata un tip principal, ce poate fi
intotdeauna precis inferat. Esenta sistemului de tipuri i n Haskell este
data de "sistemul de tipuri Hindley-Milner". Aceasta teorie a servit $i
serve$te ca baza formala pentru tipizarea din mai multe limbaje,
inclusiv Haskell , ML, Miranda (ultimul este o marca inregistrata a
firmei Research Software Ltd.). Acest tip principal este corect in
sensul ca nu este nici excesiv de general dar nici nu omite vreun tip
posibil, particular al acelei expresii. Sistemul de tipuri infereaza
automat acest tip principal. Pentru utilizator, acest tip este eel care

0 introducere fn Haskell 98 prin example- 74- Dan Popa


cuprinde toate instantele expresiei $i nimic In plus, deci eel mai mic
cu aceast~ proprietate.
Trecem Ia rAndul urm~tor:

tunde (Frunza a) =[a]


El trebuie lnteles astfel: "dac~ argumentul (despre care $tim c~ este
de tipul Arbore a) este construit cu constructorul de date Frunza $i o
valoare a, atunci rezultatul este lista formata din acel element (de
tipul a) care era plasat In nodul frunz~.
Operatorul ++ este operatorul de concatenare a dou~ liste ($i a doua
stringuri). Adaugarea unui element lntr-o list~ se face cu alt operator
(:) care va fi prezentat In subcapitolul urmator. Trecem Ia al doilea
$ablon de pattern-matching.
tunde (Ramificatie ram1 ram2) = tunde ram1 ++ tunde ram2
Ea semnifica faptul ca pentru un arbore cu subarbori legati Ia o
ramificatie lista frunzelor ce rezult~ cand se tunde arborele este
concatenarea listelor frunzelor tunse din cei doi subarbori numiti
ram1 $i ram2. Functia fiind recursiv~ . ie$irea din recursie se face In
situatia din cazul simplu, eel al frunzei.Exemplu:

I . , •t •I I • I. I •

File Sessions Sei!Jngs Help

"axy"
..
Main> tunde <Ra•ificatie (Frunza "a") (Rallificatie <Frunza "x
") <Frunza "~")))
[ .. a .. . "x"."y .. ]
Main> I

D.I~
Procesarea unul arbore.

0 introducere in Haskell 98 prin exemple - 75 - Dan Pops


Surprinz~tor ne va p~rea exemplul:

[3.4]
Main) tunde <Ra•ificatie (Frunza 'a') (Ra•ificatie (Frunza
') (Frunza 'y')))
"axy"
Main) I

Llsta de caractere e returnata ca un string.

in acest caz ne-am fi a$teptat sa obtinem o lista de caractere, nu un


string. Dar in Haskell listele de caractere $i stringurile sunt acela$i
lucru I E deci momentul s~ punem ordine in cuno$tintele pe care le
avem despre liste, ceea ce vom face in subcapitolul urmator.

3.4. Llste in Haskell

Pentru liste at~t constructorul de tip c~t $i eel de date sunt scrise Ia
tel, ca o parantez~ p~trata [ ], dar nu se scriu inaintea argumentelor.
Deci tipul "lista de elemente de tip a" nu se scrie [)a ci [a] iar lista cu
elementul care are valoarea lui a nu se scrie [ ]a ci [a). Lista vida se
scrie [ ), iar listele cu mai mutt de un element au elementele separate
prin virgule.
Example: [1,2,3] de tipul [Integer]
[(1,2) (3.4)] de tipul [(Integer,Integer)]
adica lista de perechi.
Elementele unei liste au toate acela$i tip, deci ['a',2] nu este o lista

0 introducere in Haskell 98 prin exemple - 76 - Dan Popa


valida din punct de vedere al tipului elementelor. Daca totu$i doriti sa
in$iruiti asemenea elemente atunci va trebui sa recurgeti Ia un tip
reuniune asemanatoare cu cea din exemplul cu arborele sau mai
bine zis (lasand deoparte ramificarile) Ia o lista de elemente dintr-un
tip reuniune.
Operatorul ":" este folosit pentru adaugarea elementelor intr-o lista.
El face o operatie simpla, adauga un element in fata listei. Este
oarecum similar cu "cons" din LISP. Elementele pot fi adaugate in
fata listei vide [ ],formand liste oricat de lungi. Lista [1 ,2,3] este de
fapt lista 1:(2:(3:[ ]))). Operatorul ":" este asociativ Ia dreapta deci
aceea$i lista se poate scrie 1:2:3:[ ] . in continuare dam exemple de
functii care prelucreaza liste:
- Numararea elementelor unei liste ne da ca rezultat lungimea ei:
{lista nevida e considerata formata din h - primul element, capul
listei $i coada t a listei. Notatiile sunt inspirate de cuvintele engleze$ti
head $i respectiv tail )

lungime :: [e] ->Integer


lungime [ 1 0
lungime (h:t) = 1 + lungime t
Cap3Par4Ex1 .hs

Foarte des, ca $i in acest caz, listele pot fi prelucrate recursiv,


urmand ideea ca sunt formate din cap $i coada iar coada e o lista
similara cu cea initiala. Evident, cazul cand lista data e vida asigura
ie$irea din recursie $i se va scrie primul. Atunci c~nd fncearca

0 introducere in Haskell 98 prin example - 77 - Dan Popa


altemativele disponibile pentru pattern matching, interpretoru/
Haskell le folose$fe in ordinea in care sunt scrise in program.
Ordinea lor conteaza.
- Putem extrage capul respectiv coada unei liste cu doua functii
recursive clasice, pe care le vom numi pe romane$te "cap" $i
"coada": cap :: [e) -> e
cap (h : t) = h
co ada : : [ e)-> [e)

coada (h:t) = t
Cap3Par4Ex1 .hs

Atentie: aceste functii nu sunt definite decat pentru o lista nevida.


Exemplele de pana acum pot fi comasate intr-un singur program.
lata-1 in curs de editare $i in executie:
.. .. : : • !Qi x
file l;dlt .a.ooM1aru roots ~ettings l:ielp

051i1 0t:9 ~ ~ XCb~ ~ ~~


II-- Lis le si ~
-- Tipuri utilizator cu mai •ulti constructor! de date
-- din Cap3 Pa r4 • Den Pope 30 mertie 2005
-- Lungi•ea unei lis le de elemente
.. .. (e) -> Integer
lungille
lungime (] =0
lungime (h:t) = 1 + lungille t

-- Functia care extrege capul unei lisle nevide


cap .. (e) - > e
cap (h:t) =h
-- Functia care extrage coeds unei lisle nevide I-
co ada .. ( e) - > ( e) ~
I-
coada (h:t) =t ~
L~n e 1 Col ljjNSY..L

Cap3Par4Ex1 .hs- edltarea programulul

0 introducere rn Haskell 98 prin example - 78 - Dan Popa


Cititorul care dore$te, poate gasi problema $i exercitii suplimentare
cu liste in orice carte de LISP. Experienta provenita din exercitiile cu
liste omogene (ca tip de elemente) este imediat aplicabila in Haskell.
Celelalte problema presupun un mic effort de adaptare. in imaginea
urmatoare este prezentat programul de mai inainte in actiune.
lnterpretorul raspunde Ia intrebari privind lungimea, capul $i coada
unei liste, calculate folosind functiile de mai sus:

Cap3Par4Ex1.hs
Type :? for help
Main> lungime [1.2.7.9]
4
Main> lungime [[1J.[3.4J.[5].[9.0JJ
4
Main> cap [[1J.[3.4J.[5J.[9.0JJ
[1]
Main> cap [1.2.7.9]
1
Main> coada [1.2.7.9]
[2.7.9]
Main> coada [[1J.[3.4J.[5J.[9.0JJ
[[3.4J.[5J.[9.0J]
Main> I

Cap3Par4Ex1.hs - Ia rulare funqiile extrag elemente din llste

Apropos de LISP. exista $i in Haskell o serie de functii speciala


pentru lucrul cu liste, printre care $i acel map, care prime$te o
functie $i o lista de argumente $i returneaza lista valorilor functiei
calculate pentru acele argumente (din lista data initial).
Teoria afirma cain Haskell lista este tot un tip reuniune cam ca ~i
tipul Arbore dintr-un subcapitol anterior. Reuniunea va fi formata din

0 introducers in Haskell 98 prin exemple - 79 - DanPopa


submultimea continand ca unic element lista vida (cu constructorul
ei) $i multimea datelor construite cu constructorul de date "cons"
(notat cu simbolul doua puncte),aplicat unui element de lista $i unei
liste.
Putem sa definim $i noi un asemenea tip de lista. Vom folosi insa alt
operator(:::) in locul celui obi$nuit (:).
data Lista a ListaVida
= a ::: (Lista a )
Cap3Par4Ex1.hs

Observati ca asemenea constructori de date ca ":::" sunt permi$i in


declaratiile data de$i sunt infixati, adica scri$i intre operanzii lor $i nu
inainte. va intrebati cumva cum ii recunoa$te Haskell, mai ales ca
pot fi definiti de utilizator ($i nu fac parte din limbajul de baza) ? Ei
bine, ii recunoa$te dupa o regula foarte simpla.

Constructorii de date infixati incep intotdeauna cu simbolul doua-


puncte (:).
Regula este in mod automat satisfacuta $i pentru ":", folosit Ia liste.
sa incercam acum sa aflam pentru noile feluri de liste (acest
concept de lista este pentru Haskell ceva deosebit de listele lui deci
functiile obi$nuite pentru lucrul cu liste nu se aplica) cum se scrie
functia care mascara lungimea listei.
lung : : Lista a-> Integer
l ung ListaVida 0
1 ung (a : : : 1) 1 + lu ng 1
Cap3Par4Ex2.hs

0 introducere in Haskell 98 prin exemple - 80 - Dan Popa


Sa lncercam sa rulam programul de mai sus ~i sa aflam lungimea
unei liste: lung ( 3 ::: 4 ::: ListaVida)
incepem prin a scrie programul din figura urmatoare:

4f,-~ Cap3Par4Ex2 hs 1mod1fiedj- KWnle ~


Elle fdrl eookmarks !OOIS ~edings l:felp

Liste si
Tipuri utilizator cu •ai •ulti constructor! de date
din Cap3 Par4. Dan Popa 30 •artie 2005
constructorul de date infixat TREBUIE sa inceapa cu
dar sa nu fie deja folosit ca : sau ::

data Lista a = ListaVida


a . .. (Lista a)

lung Lista a -> Integer


lung ListaVida =0
lung ( a ::: l) =1 + lung 1

Constructor de llste lnflxat deflnit de programator (Cap3Par4Ex2.hs)

Dar lncercarea de a afla lungimea listei de mai sus e~ueaza:


. " ~· . ...
File Sessions s emngs Help

Main> lung ( 3 ::: 4 ListaVida)


ERROR - Illegal Ha s kell 98 class constrain~
••• Expression lung {3 4 ::: Lis taVida
••• Type : {NuM a. Nu• {Lista a)) = > I

Main ) I

La construlrea llste operatorul e asoclatlv Ia drepata (Cap3Par4Ex2.hs)

Este clar ca am omis ceva. V-ati dat seama ce? Raspunsul este ca
scrierea listei ca mai sus se bazeaza pe (ati ghicit) asociativitatea

0 introducere in Haske/198 prin example- 81 - Dan Popa


operatorului Ia dreapta. Haskell permite sa precizati pentru operatorii
definiti de utilizator pe care treapta de prioritate sunt plasati $i cum
asociaza ei implicit in /ipsa parantezelor (Ia dreapata sau Ia st§nga
sau nedec/arat). Sintactic vorbind ::: este un operator iar din punct
de vedere semantic e un constructor de date. Abia dupA ce i se
declara nivelul de prioritate $i modul de asociere parserul Hasekll
ajunge sa recunoasca $i sa structureze expresiile scrise cu el a$a
cum ne-am dorit. in lipsa unor declaratii care sa precizeze prioritatea
$i asociativitatea, Haskell plaseazA operatorul pe un nivel de
prioritate implict ( cu numArul 9) $i ii atribuie proprietatea de
asociativitate cea mai de intalnita, asociativitetea Ia stanga. Ori
aceasta este exact ceea ce nu ne trebuia in acest caz I
#,;· Cd JP •,~E...::b hi- ~ W"lo
file EdH llookmatt.s Iools Settings titlp
I D69 0c:9 il) (.'!ol ;}([b~ ~ ~~
-- lis l e si
-- Tipuri utilizator cu •ai •ulti constructor! de date
-- din Cap3 Par4. Dan Pope 30 •artie 2005
-- conslruclorul de date infixat TREBUIE sa inceapa cu :
-- dar sa nu fie deja folosil ca : sau ::

infixr 5 :::
data Usta a =I Ust aVlda
8... (lisle a)
lung .. lisle a -> Integer
lung UstaVida =0
lung ( a : : : 1) =1 + lung 1

JL\!)! 14 Col 1 IiE[C[

Programul corect (Cap3Par4Ex2.hs)

A trebuit sa adaugam declaratia inflxr 5 ::: care plaseaza noul


operator printre operatorii infixati de pe nivelul de prioritate 5 cu
asociere implicita Ia dreapta ( r - right, inseamna dreapta; I - left,

0 introducere rn Haskell 98 prin example - 82 - DanPopa


lnseamna st~nga , ambele in limba engleza). Acum functioneaza:
• •• • (.0](01~
File Seutons Se1!1ngs Help

Cap3Par4Ex2b. hs
T ~pe :? f or hel p
Main> l ung ( 3 ::: 4 Lis taVi da)
2
Main> I
..
D. I~
Se executA corect (Cap3Par4Ex2.hs)

- Ultimul exercitiu practic din acest subcapitol va propune sa scrieti


un operator de "mapping" (similar cu map). El aplica o functie de un
argument pe toate elementele dintr-o lista ~i returneaza lista valorilor
astfel obtinute. Consider~nd ca functia are tipul polimorfic a->b ,
mapping poate fi scrisa astfel:
mapping (a -> b) -> (a] -> [b]

mapping f [) 0
mapping f (h:t) f h : maping f t
Cap3Par4Ex3.hs

• o fi'
[llo £<lll llOO"'ari.S looll ~tmngs t1...

D6 Q O ~ -~; ·(>I X Q) ID ~ s::\ ~


Lis l e si
- din Cap3 Par'!. Dan Popa 30 • artie 2005
- "Map" care apl ica o funclie pe l oatc cl e• entel e
-- unei lisl e dote
-- detorila pr1orllet11 • ai •ari a aplicarU funclic1
-- decat cea a operet1e1 "cons" . jos se poet e scrle
-- faro parant eze. renunlandu-se le
--- Mapping f (h: t ) = ( f h) : <• epping f t)

•apping .. (a->b> - > [a] - > [ b)


1111ppt ng f [] = []
eappi ng f (h:t) = f h : • eppi ng f t

•lint fCOI,;..II (NS.f} •••


~~

Cap3Par4Ex3.hs- in curs de edltare

0 introducers in Haskell 98 prin example - 83 - Dan Popa


Observatie: Functia aplicata de "mapping" asupra elementelor listei
poate fi ~i o abstractie (functie anonima) - lambda expresie.

"! • • • •

Flit Sessions Selllngs Help

Reading file "/usr/share/hugs /lib/Prelude .hs" :


Reading fil e "Cap3Par4Ex3 . hs ":
Hugs session for:
/us r/s hare/hugs /lib/Prelude.hs
Cap3Par4Ex3.hs
Type :? for he lp
Hei n> •epping (\x - > x+1) [10. 20.30 ]
[ 11. 21.311
Mai n > I
..•
D.I[E)]
Cap3Par4Ex3.hs - rularea programulul

Nu uitati ceva important: map este deja definita In Standard Prelude.


Am folosit-o ca exercitiu doar din motive didactice, ca prim exemplu
de functie care are printre parametri un parametru de tip functie
polimorfica.

Urmatoarea imagine convinge ca lntr-adevar map era predefinita.

t .. (() I • I 1 - • t • • JLD LJC.


File Sessions Settings Help
Main> mapping (\x - > x+1) [10.20.30]
[11.21.31]
Main) •ap (\x - > x+1) [10.20. 30]
[11.21.31]
Ma in) I
D.[~
Cap3Par4Ex3.hs- lar map era predeflnlti

0 introducere in Haskell 98 prin example - 84 - Dan Popa


3.5. Llste '' Jar liste, liste de caractere, stringuri
Unele dintre urmatoarele example de prelucrari de Jiste sunt
realizate folosind cateva mici artificii. Exista ~i in universul exercitiilor
cu liste unele mai u~oare ~i altele mai grele. De exemplu este u~or

sa scrii o functie care returneaza:


- primul element at unei liste (am ~i scris-o}
- ultimul element at unei liste
- lungimea unei liste (am ~i scris-o)
- at n-Jea element at unei liste
Primul element se obtine imediat, e caput listei, am vazut functia in
subcapitolul 3.4. Elementul ultim este ... primul element al unei liste
de un element (deoarece in acest caz primul e ~i ultimul} sau ultimul
element al cozii pentru o altfel de lista.
u l tim [a] -> a
u l tim (x : [] ) = X

ul tim (h : t) u l tim
Cap3Par5Ex2-ultlm.hs

Scrieti programul, eventual insotit de comentarii:


~-- C;.pJParSE-'12-ulirm hi· KW'II~ mol
f ile ~d~ .Bookmarb Iools ;ielllngs l:!elp

D 5 9 0~ rt; ~ X Eb~ p; ~~
-- Ulti •ul ele•ent al unei list e
-- Dan Popa 31 •artie 2005

ultb :: (a] -> a


ultb (x:[] ) = X
ulth (h : t) = ultb t

line 3 Col I fiNSI I


Cap3Par5Ex2-ultlm.hs - Tn curs de edltare

0 introducere in Haskell 98 prin exemple - 85 - Dan Popa


$i apoi rulati-1 cerand interpretorului sa afle ultimul element al unor
liste:
.. . , .
File SessiOI'IS Se1bngs Help

~ugs session for :


/usr/share/hugs/lib/Prelude.hs
~ap3ParSEx2 -u lti•.hs
~ype :? f or help
~~in> ulti• ( 1. 3. 4 .8 .9 ] ..
~
~ain> ulti• (90]
~0
D.I~
Cap3Par5Ex2-ultlm.hs - rularea

Calculul lungimii unei liste se face U$Or cu o functie recursiva cu un


argument. Prin contrast, a afla al n-lea element, presupune sa
scriem o functie cu doua argumente:

$i dupa cum se vede in imaginea urmatoare, gase$te corect


elementul cerut:

, ,... Cap3ParSEx3-element hs • f.W•.te


Elle fdit ~ool\mar*.s Iools lie11ings t!elp

D6/it O ~ It) ~ )(~~~~~


-- Al n-lea ele• ent dintr- o lista
-- Dan Popa - 31 •artie 2005

ele•ent .. I nteger -> [a] -> a


ele•ent 1 (h : t) =h
eluent n (h :t) =el e•ent (n- 1) t

Line· 4 Cot 42 I INS I I


-
Cap3Par5Ex3-.hs - in curs de edltare
Cap3Par5Ex3-.hs - Tn curs de executle

0 introducere In Haskell 98 prin exemple - 86 - DanPopa


File Settings Help

/usr/s hare/hugs/lib/Prelude.hs
Cap3Par5Ex3- element . hs
Type :? for help
Hain> element 3 (1.2.3 . 6.7.9]
3
Main> element 2 (101.103.208]
103
Main> I
[l

lnversarea unei liste este Ins~ o problem~ cava mai grea, daca
lncercati s-o rezolvati cu o singur~ functie cu un singur argument.
incercati aceasta daca doriti I
0 solutie mai elegant~ este s~ folositi o functie cu dou~ argumente
transferfmd elementele din primul argument in a/ doilea.
Ambele argumente sunt, evident, liste. Aceast~ "tehnic~". numit~

accumulative programming este folosit~ ~i de programatorii In


Prolog.
Observati cum capul h luat din fata primei liste ajunge In a doua, cu
ocazia apelului recursiv.

rl :: [a] -> [a]-> [a ]

rl [) lista lista
rl (h : t ) lista rl t (h : lista )
Cap3Par5Ex1-reverse.hs

Cu ajutorul acestei functii auxiliare rl functia cerut~ este u~or de

scris:
r evers [a) -> [a)

0 introducers Tn Haskell 98 prin example - 87 - Dan Popa


revers l = rl l []
Cap3Par5Ex1-reverseohs • contlnuarea

lata ~i o problema ceva mai complexa, care va provoaca Ia scrierea


unui program pu1in mai lung: §tiind ca un string este o lista de
caractere, calculati valoarea unui numar (intreg) dat sub forma de
string tara sa folositi functii de bibliotecao
Solutie: Valoarea unei cifre se calculeaza imediat, chiar daca nu
apelati Ia functii de conversie din caracter in numar:
vale 0 0 Char -> Integer
vale f 0 I
0
vale ' 1 ' 1
vale ' 2 ' 2
vale • 3 ' 3
vale ' 4 ' 4
vale ' 5 ' 5
v a le ' 6 ' 6
v ale ' 7 ' 7
v ale ' 8 ' 8
va le ' 9 ' 9
Cap3Par5Ex4-atolohs

Sau, daca ~titi despre cifre ca au coduri ASCII succesive, puteti scrie
o functie mai scurta:
va l e : :Char - > Integer
v ale x = ord x - ord ' 0 '
000 dar aceasta solutie eficienta va obliga sa importati biblioteca in

0 introducere in Haskell 98 prin example - 88 - DanPopa


care se afla functia ord. (Cautati functia ~i numele bibliotecii in care a
fost plasata folosind Hoogle: haskell.org/hoogle . Ceea ce inca nu v-
am aratat in nici o imagine. lmportati biblioteca cu import <nume>)
0 lista de cifre - e drept, inversata - o putem transforma u~or in
valoarea numarului. De exemplu, fie valli functia care face
transformarea.
valli :: [Char] -> Integer
valli [] = 0
v alli [a]= vale a
valli (h: t ) = vale h + 10 * valli t
Cap3Par5Ex4-atol.hs

Prin apeluri recursive ea va calcula succesiv pe valli [ '4','2','1' 1ca


fiind:
4 + 10 * valli [ '2','1'] =4 +10 * ( 2 + 10 * valli [ '1' 1) = 4 + 10 * (2 + 10
* 1)=124
Acum nu ne mai ram~ne dec~t sa obtinem valoarea intregului dat ca
string cu ajutorul functiei care inverseaza o lista ~i al functiei valli.

atoi : : [Char] -> Integer


atoi 1 = v alli ( reve rs l )
Cap3Par5Ex4-atol.hs

Cap3Par5Ex4-atol.hs- in tlmpul edltirii

0 introducere in Haskell 98 prin exempfe - 89 - DanPopa


•J LQJ L~
E,ll& ,li;dll li!OOk.m81KS !OOIS S.ettlngs l:ialp

D ~ .. .. ! •• .. ' .
-- A File Sessions Settings Help

-- D Hugs s e ssion for :


/us r/s hare/hugs /lib/Prelude .hs
rl Cap3Par5Ex4- atoi.hs
rl [ T!Jpe : ? for help
rl (I Main> a toi "12 3"
123
reve Main> atoi "6789"
reve 6789

vale D. I~
vale '0' - 0
vale '1' = 12
vale '2' =3
vale '3' =
val e '4' = 54
va l e '5' =
vale '6' = 67
vale '7' =8
vale '8' =
vale '9' = 90
vale - =
v a l li .. [Char] - > Integer
valli [] =0
valli [a] = vale a
valli (h:t.) = vale h + 10 w valli t

atoi .. [Char] - > Integer


atoi l = valli (revers 1)
Line: 9 Col. 2 1 j IN S

Exemplul, a~a cum ar~ta In timpul editarii este reprodus mai sus.
Cap3Par5Ex4-atoi.hs • in timpul rutaril

Rulati programul ca de obicei ~i testati functia: Retineti: in programul


de mai lnainte se putea Ia tel de bine folosi [Char] ca ~i String,
acestea fiind sinonime. in subcapitolul urm~tor vom ar~ta cum se
definesc asemenea sinonime cu ajutorul declaratiei type.

3.6. Tipurl slnonime, declaratia type

0 introducere in Haskell 98 prin example - 90 - Dan Popa


in cartea "A Gentle Introduction to Haskell 98" scrisA de Paul Hudak,
John Peterson ~i Joseph H. Fasel, autorii explica Ia ce se folosesc
aceste sinonime. Scopul introducerii lor in limbaj este simplificarea
scrierii tipurilor (lucru important dacA se lucreazA cu tipuri compuse,
complicate) ~i posibilitatea de a face programele mai clare prin
atribuirea unor nume sugestive diferitelor tipuri de date pe care le
folositi mai des. Este mai u~or de citit codul daca in el apare o
functie:
caut :: Lista -> Pozitie -> Valoare
de~t dacA in el apare:
caut :: [Int] -> Int -> Int
Atentie: type se folose~te atunci cand nu inventam un tip nou ci dam
un nume nou unui tip construibil cu constructorii de tip deja existenti.
lata diteva exemple:

type String = [ Char ]


type Persoana = Nume, Adresa

unde Nume ~i Adresa servesc Ia a indica alte doua tipuri definite de


utilizator, fie prin sinonimie fie prin declaratii data.

type Nume = String


data Adresa = Necunoscuta I Ad.r esa String

Un aft exemplu tipic este lista de asociere a valorilor din doua tipuri

0 introducere in Haskell 98 prin example - 91 - Dan Popa


oarecare a,b:

type ListaAsoc a b =[ (a , b ) ]

Observati deci ca declaratia type ne permite sa declram noi nume $i


pentru tipuri polimorfice. Practic, aceasta lnseamna ca se pot folosi
variabile de tip In declaratia type. Cu declaratia de mai sus tipul "lista
de perechi nume de variabila - valoare lntreaga" se scrie simplu:
ListaAsoc String Integer inlocuind variabilele de tip a $i b cu
tipurile String $i Integer.

3.7. Multimi implicite reprezentate ca liste (sau "list


comprehensions")

$tim de Ia lectiile de matematica faptul ca o multime se poate da in


doua moduri:
a) enum erand elementele sale - scriind o lista a lor tara duplicate.
b) dand o definitie generala ( o proprietate ) a elementelor ce vor fi
incluse in multime. Exemple:
a) { 1, 2, 3, 4, 5 }
b) { f(x) Ix E X} { (x,y) Ix E A , y E B}
in Haskell listele pot fi descrise $i ele In aceste doua stiluri.
Deocamdata avusesem de-a face numai cu liste scrise In stilul de Ia
punctul a:
a) [1 , 2, 3,4,5]

0 introducere In Haskell 98 prin exemple - 92 - Dan Popa


Dar putem scrie deasemenea liste, ne asigurA Paul, John ~i Joseph
in cartea citata, in stilul de Ia punctul b:
b) [fx I x <- xs ] [ (x,y) I x <- as , y <- bs ]
unde , xs, as, bs sunt de asemenea liste. Autorii mai sus citati ~i

probabil ~i alti membri ai comunitatii Haskell (vedeti $i


www.haskell.org) obi$nuiesc sa noteze o variabiiA pe care potrivirea
de $abloane (eng. pattern matching) ova lega Ia o lista cu acela$i
nume ca al variabilei cap de lista dar urmata de litera s. lar litera
dintai este inspirata de numele listei. De exemplu, ceea ce
matematicianul ar nota prin multimea M, programatorul in Haskell va
nota prin m : ms evidentiind capul listei m $i lista de elemente
similare lui m ("m-urile" - Ia plural) ms.
Semnificatia notatiei [ (x,y) 1 x <- as , y <- bs ] este de produs
cartezian. Apartenentele din notatia matematica se transformA in
Haskell in operatori "<-" (care chiar seamAna oarecum cu semnul
apartenentei dar pot produce $i duplicate). Aceste "apartenente"
sunt numite in Haskell generatori. Atunci cand sunt mai multi
generatori sintaxa cere ca ei sa fie separati prin virgu/8.
Ca exemplu, evaluati: [ (x,y) 1 x <- (1 ,2,3] , y <- [ 'a','b'] 1
$i veti obtine: [ (1 ,'a') , (1 ,'b') , (2,'a'), (2,'b') , (3,'a') , (3,'b') 1
a$a cum se vede ~~ in imaginea urmatoare - promptul sistemului
poate sa difere.
Produsul cartezian a doui llste - in maniera "list comprehension"

0 lntroducere in Haskell 98 prin example - 93 - Dan Popa


... ... . .. . .
. -
File Sessions Se1!1ngs Help

Haskell 98 mode: Res tart with command line option - 98 to enab


..
le extensions
Readi ng f i le " /us r/share/hugs/lib/Prelude.hs" :

Hugs session for:


/us r/s hare/hugs /li b/Prelude . hs
Type :? for help
Prelude> [ (x.y) I x <- [1.2.3J • y <- [ ' a' .'b'J J
( <1. •a. ) • <1 •• b. ) • <2 •• a. ) • (2 •• b. ) • <3 •• a. ) • (3 •• b. ) J
Prelude> I

Ct[~

Plus: Putem sa ata$am $i ni$te garzi (predicate) acestor generatori,


limitandu-le astfel actiunea doar Ia valorile care satisfac conditiile din
fiecare garda. In cartea citata algoritmul Quicksort este descris
folosind generatoare $i garzi astfel:
#.r• Cap3Par7Ex1-qulcksorths- KWnte
Elle fdlt aookmarks Iools ~etlfngs !:felp

D5& 0 ~ ~ ·~ X~~~~~
- - Quicksort
-- Cap3Par7Ex1
- - Oupa "A Gentle Introduction to Haskell 98" pg 9

quick .... [Integer] -> [Integer]


quick [] = []
quick (h:t) = quick [ y I y <- t . y < h ]
++ [h)
++ quick [y I y <- t • y >= h ]

line: 6 Col 37 ) INS I I

Un algorltm de sortare: Cap3Par7Ex1 .hs

Sortarea unei liste (h:t) se face astfel: se sorteaza lista elementelor


mai mici decat capul h, se concateneaza cu lista cu capul [h] apoi
cu lista sortata a elementelor mai mari decat capul h. Concatenarea

0 introducere in Haskell 98 prin exemple - 94 - Dan Popa


celor trei liste se face cu operatorul ++. G~rzile sunt y < h ~i y >= h
~i se separ~ de generatori tot prin virgule. Acest algoritm ocup~
minim 3 ~i maxim 5 rAnduri spre deosebire de variantele sale scrise
In Pascal sau C care pot fi de circa 10 ori mai lungi ! Haskell, datorit~
notatiilor sale inspirate din matematic~ se dovede~te extrem de facil
de folosit Ia o serie de probleme.
Algoritmul de mai sus functioneaz~ . dup~ cum se vede, perfect:

• IID][X
File Sessions Settings Help

Main> ~
Main) quick [1.10.9.234.7.78.( - 1) ]
[- 1.1.7.9.10.78.234] -
~
Main) I ";'-
D.llfrj]
Cap3Par7Ex1.hs - qulck(sort) ordonAnd o llsti de intregl

3.8. Secvente aritmetlce


Tot In categoria listelor, Haskell include ~i secventele aritmetice,
asem~n~toare cumva tipurilor subdomeniu din limbajul Pascal. Le
puteti considera ca ni~te notatii alternative pentru liste lungi de
elemente succesive (dintr-o multime ordonat~).
Succesiunile de elemente sunt date precizAnd primul element,
ultimul ~i punAnd doua puncte succesive lntre ele. Pasul secventei
este ori 1 ori este dat de diferenta primelor dou~ elemente.

0 introducere in Haskell 98 prin exemple - 95 - Dan Popa


"§-.. file /usr/share/hugs/lib/Prelude hs- Konqueror
l.ocation fdit Yiew {io flookmarl<.s Iools §.ettings Yf.lndow J:!.elp

Cap3Par8- opera~ll asupra llstelor, definite in blblloteca standard

0 introducere in Haskell 98 prin exemple - 96 - DanPopa


Example de liste de numere scrise folosind " ..":


(1 .. 1OJ lnseamna [1,2,3,4,5,6,7,8,9,10)
(1 ,3 .. 10] lnseamna [1,3,5,7,9]
(1,3 .. ) lnseamna [1 ,3,5,7,9...) rr

Observati ca exista ~i secvente aritmetice infinite. Este un element


nou. Haskell permite sa lucram ~i cu astfel de liste.

ll

0 introducere in Haskell 98 prin exemple - 97 - DanPopa

.,
~-
Structuri de date ~i alte notiuni utile

4.1. Structuri de date infinite


Orice utilizator de liste ~tie ca o lista se termina cu NIL, NULL sau
altceva care inseamna "lista vida". Cu exceptia listelor circulare care
prin natura lor, evident, nu se termina.
Umbaju/ Haskell are posibilitatea de a Iuera cu liste infinite. Vom
incepe cu un exemplu banal, lista infinita de unitati. Dar intruc~t ea
nu poate fi afi~ata in intregime vom face unele pregatiri. Scriem in
program declaratia listei infinite de unitM urmata de o functie care
extrage primele n elemente dintr-o lista:
-- Cap4Par1Ex1 , Lista infinita de unitati
units = 1 : units
first . . Int -> [a) -> [a 1
first 0 [1
first [) [)

first 1 (h : t) [h)
first n (h : t) h : first (n-1) t
Cap4Par1 Ex1 .hs

A~a arata programul de mai sus in cursul editarii:

0 introducere In Haskell 98 prin exemple - 98 - DanPopa


~-- Cap<:Par1E.<1 h>- KWrle D l!J IJ
E•le Edn a ookmatks I ools ,SeiiJngs l:!elp

Dolilo~ 'IJ tJJ X ~ ~ ~ ~~


-- Cap4Par1Ex1 ~
-- Tipuri "infinite" de date - lis ta infinite de unitati
- - Dan Popa - Has kell By Exa•ples . 11 aprilie 200~

-- Lis t a for•ata din 1 s i ea insasi

units = 1 -- units
fir s t : :Int - > (a) - > (a)
firs t 0 _ = (]
firs t _ (] = (]
firs t 1 (h:t) = (h) ..•
firs t n (h:t) =h : first (n- 1> t
Line: 3 Col 51 I INS I I

Cap4Par1Ex1.hs- in cursul editirii

Ca sa putem afi$a, macar partial, lista infinita am recurs Ia un true.


Functia first de mai sus extrage dintr-o lista atatea elemente cate
unitati are primul argument (lntreg). Sunt chiar primele n, In ordine.
Evident, primele 0 elemente din orice lista nu pot forma decat o lista
vida. Primele n elemente dintr-o lista vida formeaza (convenim) toto
lista vida. Primul element dintr-o lista nevida este chiar capul
acesteia (h). lar primele n elemente dintr-o lista (cu eel putin doua
elemente) sunt capul (h) urmat de primele n-1 elemente din coada
listei date (t - de Ia tail). Ca proba, incercati $i dumneavoastra sa
evaluati: first 1 [1 ,3,4,5] $i rezulta [1]
first 2 [1 ,3,4,5] $i rezulta [1 ,3]
first 4 [1 ,3,4,5] $i rezulta [1 ,3,4,5]
first 5 [1 ,3,4,5] $i rezulta []
Extragerea prlmelor elemente dlntr-o llsti

0 introducere in Haskell 98 prin example - 99 - Dan Popa


File Souoono Somngo Help

[1.3.4]
Me~n> f~rst 4 [1.3.4.5]
[1.3.4 .5]
Mein) fir st 5 [1.3.4.5]
[1.3.4.5]
Mein> first 0 [1.3.4.5]
(]
Mein> fir s t 100 units
[1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1
.1. 1 .1.1 .1.1 .1.1.1.1.1.1.1.1.1.1
.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1
.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1
.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1
.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1
.1.1.1.1]
Main> I
D.!l6!
Pe urma lncercati sa evaluati: first 100 units. Doriti sa vedeti
primele 1000 de elemente din lista de unitati ? lata-le:

M"l.n ) ft r t~l. 1000 unll."'


[ 1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.
1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1 . 1 .1.1.1.1.1.1. 1.1. 1. 1.1
.1.1. 1 . 1 .1.1.1.1. 1 .1.1. 1 .1.1. 1.1.1. 1.1. 1 . 1 .1.1.1.1.1.1.1.1.1. 1 .1.1.1.1.
1. t. t. 1.1.1. 1.1 . 1 . t .1.1.1.1 .1 . 1. 1. 1.1 .1 .1. t .1. 1. 1. 1.1 .1. t .1. 1.1 .1.1 .1. 1
.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1 .1.1.1.1.1.1.1.1.
1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1
.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.
1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1 .1.1. 1 .1. 1.1.1.1.1.1.1.1.1 .1.1
.1. 1 .1.1.1. 1 .1.1. 1 .1.1. 1 . 1 .1. 1 .1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.
1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1 .1.1.1 .1.1.1 .1.1
. t . 1. 1. 1. t. t . 1 • t . t . 1. t . 1 • 1 • t . 1 . 1. 1 • t . t . 1 . t . 1 . 1 . 1 . 1 . 1 . 1 . 1 .1 .1 • 1 • t. 1 • t . 1.
1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1
.1.1. 1 .1.1.1.1.1.1.1.1.1.1.1. 1 .1.1.1.1.1.1.1.1. 1 .1.1.1.1.1.1.1.1.1.1.1.
1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1 . 1.1.1.1.1.1 .1. 1.1.1.1.1.1.1.1.1.1.1
.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1. 1.1.1. 1 . 1.1.1.1.1.
1.1 .1 .1.1.1.1.1.1.1.1.1.1.1 . 1.1.1.1.1.1.1.1.1.1.1.1.1.1. 1 .1.1.1.1.1.1.1
.1.1. 1 . 1 . 1 . 1 .1.1.1.1.1.1.1.1. 1 .1.1. 1 . 1 .1. 1 .1.1. 1 .1.1.1.1.1.1.1.1.1.1.1.
1. 1. t .1.1. t .1.1.1. 1 .1.1. 1 .1 . 1 . 1.1.1. 1.1 • .t . t . L 1. t. I .1.1. t. 1. 1.1 .1.1 .1. 1
.1.1.1.1.1.1.1. 1.1. 1.1.1.1.1 . 1.1.1.1.1.1.1.1.1.1.1.1.1 .1.1.1.1.1. 1.1.1.
1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1 .1.1
. 1 .1.1.1. 1 .1.1.1.1.1.1.1.1.1.1.1.1.1.1.1. 1 .1.1.1.1.1.1.1.1.1.1.1.1.1.1.
1.1.1.1.1.1.1.1 .1. 1.1.1 . 1.1 . 1 .1. 1.1 .1.1. 1 . 1.1.1 .1.1.1.1.1.1.1.1.1.1.1.1
.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.
1.1.1.1.1.1.1.1.1.1.1.1.1.1 .1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1 .1.1.1
.1.1. t . t . t .1. t. t .1. t. t .1. t . t. t .. 1. 1 .1.1. t .1 .1. 1.1 .1.1 .1 .1.1 .1 .1.1.1 .1.1.
1 .1. 1.1 .1.1.1.1.1.1. 1 .1.1.1.1.1.1.1.1.1.1. 1.1.1 .1.1.1.1.1.1.1. 1.1.1.1.1
.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.
1.1.1.1.1.1.1.1.1.1 . 1 .1.1.1.1.1.1.1.1.1.1.1.1.1.1. 1 .1.1. 1 .1. 1.1.1.1 .1. 1
.1.1.t.t.t.ll
Moln> I
D.lle
Prlmele o mle de unltatl din llsta lnflnltl de unlt11J: units

Tastati Ia promterul interpretorului: first 1000 units, rezultatul va

0 introducere fn Haskell 98 prin example - 100 - Dan Popa


ocupa proportional mai mult loc pe ecran.
incercati apoi sa afi~ati lntreaga lista infinita de unitati. Haskell va
face o tentativa de a o parcurge element cu element (intrAnd In
realitate intr-un ciclu deoarece coada lui units este tot units) iar pe
ecran va curge un ~ir de elemete 1 care nu se mai termina .
Puteti face In acest caz unul din cele doua lucruri de mai jos:
- cu CTRL-Z sa opriti interpretorul Hugs
- cu CTRL-C sa opriti afi~area listei de date care nu se mai termina,
aceasta tara a parasi interpretorul. Hugs va anunta doar:
{ Interrupted ! } ~i va returna prompterul.

in exemplul urmator este data o alta definitie a listei infinite de


unitati.
otherunits = 1 : 1 : otherunits
Cap4Par1 Ex1 .hs

Din nefericire Haskell nu poate verifica egalitatea acestor liste prin


comparatie directa, element cu element - ceea ce era de a~teptat

fiind verba de liste infinite - a~a ca nu dati spre evaluare expresia


uni ts = otheruni ts deoarece (In locul True-ului a~teptat) veti
ramane sa a~teptati raspunsulla infinit.

(Nu uitati sa lntrerupeti executia tastand CTRL-C daca ati lncercat


totu~i. )

Cap4Par1 Ex2.hs- doui lisle Infinite de unltitl

0 introducere in Haske/198 prin exemple - 101 - Dan Popa


' '., .,
Flit Stuoons Stt.ngs Help

Main> units == otherunits


<Interrupted!}

Main> first 100 ot herunits


[1.1. 1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.
1 .1.1. 1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1 .1.1.1 .1 .1.1.1.1
.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1]
Main> I
D. I~
~ ...~ '""'
Cap4Par1Ex2.hs- se pot afl~ doar un numar flnit de uni~

Programarea cu liste infinite pune unele problema noi. Listele infinite


sunt deosebite de listele finite. Deosebirea se observa in cazul
acelor operatii care ar presupune verificarea intregii liste pentru
aflarea rezultatului. Ne a$teptam ca ele sa puna problema. in locul
lor va trebui sa lucram folosind operatii care NU presupun
parcurgerea intregii liste. Sa incercam sa dam un exemplu. Fie
listele infinite de mai jos (posibile valori ale unor variabile din
program) $i sa presupunem ca dorim sa le comparam. lata o
varianta U$Or modificata a programului anterior:
units 1 units
otherunits 1 2 : otherunits
Cap4Par1Ex3.hs
,. • !P!iii;
Eilt Ea~ aooMiaru Iools serungs 1::1e1p

D6/l1 0 c:9 "' <"' X Cb~ ~ ~~


-- Cap4Par1Ex3
-- Tipuri " infinite" de date - li s ts infmita de unit..ati
F
-- Dan Pope - Haskell By Exa•pl es . 11 aprilie 2005
- - Lisle for•ata din 1 si ea insasi
units =1 : units
otherunits =1 : 2: otherunits r:..
I[L••• 1c·ol 1s fiNs r_r I

0 introducere fn Haskell 98 prin example - 102 - Dan Popa


Cap4Par1Ex3.hs - doui llste Infinite dlferlte

Ca urmare a modific~rii, lista a doua va avea elementele altern~nd :


rora]fJC
File Stulons Stllingt Help
ep4Par1Ex3.hs
Tvpe :? for help
Main> fir s l 10 unils
(1.1.1.1.1.1.1.1.1.1]
Main> fir s l 10 olherunils
[1.2.1.2.1.2.1.2.1.2]
Main> I
D.!~
Cap4Par1Ex2.hs- doui llste Infinite de unltltl- Tnceputurile lor

0 operatie care se face U$Or i ntra asemenea liste infinite este cea
furnizat~ de operatorul/=. Comparatia units /= otherunits va
furniza imediat r~spunsul a$teptat, True. De asemenea not
(units /= oth erunits) va furniza un r~spuns in timp finit:False.
• a •.

Main> firs~ 10 uni~s


[1.1.1.1.1.1.1.1.1.1]
Main) fir s t 10 otherunits
(1 . 2 .1. 2.1.2. 1. 2.1.2]
Main) unit s t = otherunits
True
Main ) not {units / = otherunit s )
False
Main) not {units / = uni~s)
{Interrupted I}

MaJ. n> I

D.I~
Compara111 fntre llste Infinite

in volumul semnat de Paul, John $i Joseph, Ia pg. 14, g~siti un alt


exemplu, lista numerelor naturale de Ia n Ia infinit.
ntoinf n = n : ntoinf (n+l)
Cap4Par1 Ex4.hs

S~ afi$~m primele 20 de numere naturale strict pozitive l Folositi


comanda:
first 20 (ntoinf 1)

0 introducere in Haskell 98 prin exemple - 103 - Dan Popa


... $i obtineti raspunsul din imaginea de mai jos:

' •• • • ~ t • • • • •

File Sessions Selllngs Help

Reading file "/usr/share/hugs/lib/P


relude . hs •• :
Reading file "Cap4Par1Ex4. hs •• :
Hugs session for:
/usr/share/hugs/l i b/Prelude.hs
Cap4Par1Ex4.hs
TI:Jpe :? f or help
Main> fi rst 20 (ntoinf 1)
[1.2 . 3 .4. 5 . 6.7.8.9.10.11.12.13.14.1
5.16.17.18.19.20]
Main> I
[lj~
Cap4Par1Ex4.hs- prlmele 20 de numere naturale

Daca ati fi dorit lista primelor 20 de numere incepand cu zero ar fi


trebuit sa tastati:
first 20 (ntoinf 0)
$i obtineati raspunsul vizibil pe ultimul rand din imaginea care
urmeaza :

0 introducers Fn Haskell 98 prin example - 104 - DanPopa


• • I • , I l o ' •

File Seulons senlngs Help

Reading file "Ca p4Par1Ex4.hs ":


Hugs s ession for :
/us r/s hare/hugs /lib/Prelude.hs
Cap4Par1Ex4 .hs
T~:.~pe :? for help
Main> first 20 (ntoinf 1)
(1. 2 . 3 .4.5.6.7.8.9. 10.11.12 .13.14 . 1
5 .16.17.18.19. 20]
Main> first 20 (ntoinf 0)
(0.1.2.3. 4. 5.6.7.8.9.10.11.12.13. 14
.15 .16.17.18.19]
Main> I
D.latJ.l
Cap4Par1Ex4.hs- primele 20 de numere naturale (de Ia 1 sau dela 0)

Va intereseaza lista patratelor numerelor naturale diferite de zero ?


Chiar daca nu putem s-o afi$8m Tn Tntregime Haskell ne permite s-o
descriem ca mai }os afirmand ca se aplica operatorul de ridicare Ia
patrat (care e o functie de Ia l ntregi Ia Tntregi) pe toate elementele
listei numereleor strict pozitive. Faptul ca lntr-un limbaj a:?a ceva
este posibil se datoreaza, In cazul Haskell-ului, felul cum se fac
evaluarile expresiilor - In maniera lazy evaluation - evaluari
am=nate. Deci putem ridica numerele din toata lista infinita Ia
patrat II (Dar repet, calculele vor fi facute de fapt mult mai tarziu,
dupa nevoia de patrate din aceasta lista: exact cele necesare se vor
calcula, nu mai multe.)

p atrate map ( "2 ) ( ntoinf 1 )


Cap4Par1 Ex4.hs - contlnuare

Primele douazeci de patrate perfecte se pot deci afi:?a acum tasHind:


first 20 patrate

0 introducere fn Haskell 98 prin exemple - 105 - Dan Popa


sau scriind direct: first 20 (map ("'•2) (ntoinf 1))

. , ., ~~
File Sessions seumgs Help

Cap4Par1Ex4 .hs •
Type :? f or help
Main> firs t 20 (nt oinf 1)
[1.2.3.4.5.6.7.8 . 9.10 .11 . 12.13.14 .1
5 .16. 17.18 .19.20]
Main> firs t 20 (ntoinf 0)
[0.1.2.3.4.5.6.7.8.9.10.11.12.13.14
.15.16 .17.18.19 ]
Main> fir s t 20 <•ap <A2) (ntoinf 1>
)
[1. 4 .9.16 . 25.36.49 . 64 . 81 .100.121.14
4.169.196.225.256.289 . 324.361.400]
Main) I
O.l~
Cap4Par1 Ex4.hs- primele 20 de pltrate ale unor numere naturale

in lipsa functiei first care a fost scrisa de noi este recomandat sa


folositi o functie din biblioteca standard care face exact acela~i lucru,
extrage primele n elemente dintr-o lista. Este functia take (in limba
engleza - to take inseamna a lua). Efectul, a~a cum se vede, este
acela~i :

FUe Saaa1ona Setting-s Hatp

[1. 2 .3.4.5.6.7.8.9.10.11.12.13.14.1 ~
5.16.17. 18.19.20]
Hain> firs t 20 (ntoinf 0)
[0.1.2.3.4.5.6.7.8.9.10.11 .12 .13.14
. 15 .16.17.18.19]
Ha i n > fi r s t 20 <•ap <A2) (nt o i nf 1>
)
[ 1.4.9.16.25.36.49.64.81.100.121.14
4.169.196.225.256 . 289.324. 361. 400]
Ma in > t a ke 20 <• ap (A2> ( n toinf 1))
[1.4.9.16. 25.36 .49 .64.81.100.121.144.169.196
. 225.256.289.324 .361.400] ~
Mai n > I ~
D.l!f11
Prlmele 20 de pitrate ale unor numere naturale selectate cu take

Explicatii: map, a~a cum spuneam, prime~te doua argumente care

0 introducers Fn Haskell 98 prin example - 106 - Dan Popa


sunt o functie (cu un argument) $i o lista de valori pe care va aplica
functia. Lista poate $i o lista infinita, a$a cum ati vazut mai sus. Map
aplica functia pe elementele listei $i intoarce lista valorilor obtinute.
Functia aplicata poate ti practic orice: un operator binar careia i-am
fixat un argment, o functie de un argument data nominal, prin nume
sau o functie anonima, data printr-o lambda expresie). incercati de
exemplu: map (\x-> x+1) (1 ,2,3] Veti obtine imediat rezultatul
a$teptat: [2,3,4]. lata $i alte example:
I , I! •· •, • J[D X
File Sessions Selllngs Help

Main) •ap (\x - > x•1) [1.2.3] ..


[2.3. 4]
Main) •ap ( ~2) (1.2.3]
[1. 4. 9]
Main) take 1 [1.2.4]
[1]
Main) take 2 ( 1.2.4]
[1.2]
Main> take 4 [1 . 2 .4]
[1.2.4]
D. I~
Utlllzare a funqlilor map •I take

Operatorul ("2) este o functie care-$i ridica argumentul Ia patrat. E


obtinut din operatorul infixat 1\ caruia i s-a dat un al doilea argument.
(Acest mod de a construi dintr-o functie alta, fixand cateva
argumente este un loc comun al programarii In Haskell.) Functia
anonima astfel obtinuta (Notati $i acest mod nou de a construi o
functie anonima .0 este similara ca efect clasicului Sqr(.. ) din limbajul
Pascal. in imaginea anterioara sunt vizibile efectele folosirii lui map,
take $i ( "2). lar acum este momentul sa vedem ce alte functii de
manipulare a listelor ne ofera biblioteca Standard (Prelude).

0 introducere rn Haskell 98 prin exemple - 107 - DanPopa


4.2. Func1il care manipuleazi liste, extrase din blblloteca
Prelude

Biblioteca Standard (Prelude) poate fi considerat~ un tel de


Wonderland al program~rii functionale. Merit~ s~-i acordati o atentie
deosebit~ . A fest scrisa de niste experti, foarte buni cunoscatori ai
program~rii. Primele sapte minuni ale acestei lumi pe care doresc s~
le prezint sunt functii care opereaz~ asupra listelor. Le aveti Ia
dispozitie intotdeauna si le puteti folosi cu orice tel de liste deoarece
sunt functii polimorfice. Unii autori numesc acest polimorfism realizat
cu ajutorul variabilelor de tip: polimorfism parametric. Evident ca nu
e nevoie s~ mai scrieti odat~ aceste example deoarece sunt deja
scrise.

Prima "minune" e functia head care extrage capul unei liste:

h ead :: [a ] - >a
h ead ( x: ) = X

cap4Par2Ex1 .hs

A doua este functia last care extrage ultimul element dintr-o list~ :
Lista e considerat~ ca fiind format~ din x, primul element, care poate
fi oricare, (motiv pentru care e folosit _ ) urmat de restullistei,noatat
xs.

0 introducere in Haskell 98 prin example - 108 - Dan Popa


last [a] -> a
last [x] = x
last (_ :xs) = last x s
Cap4Par2Ex2.hs

Last aplicata listei cu un element da chiar acel element, care e $i


ultimul. Last aplicata unei liste formate din orice cap $i coada xs
trebuie sa dea ca raspuns ultimul element din coada. Dupa o serie
de apeluri recursive succesive lungimea listei scade Ia 1 moment in
care pattern matching-ul va aplica prima regula $i va retuma
rezultatul, singurul element, ie$ind din recursie.
A treia "minune", functia care extrage coada unei liste se nume$te
(in Standard Prelude) tail :
tail [a] -> [a)

tail ( :xs ) xs
Cap4Par2Ex3.hs

Nu conteaza cine e primul element. in momentul potrivirii $ablonului


lista e descompusa in cap (care e ignorat, poate fi oricare) $i coada,
aceasta fiind retumata ca rezultat.

A patra "minune", e functia lnlt care returneaza partea inffiala a unei


liste exceptfmd ultimul element considerat final.
init :: [a] -> (a )
init [x] []

init (x : xs) x : init xs


Cap4Par2Ex4.hs

0 introducere in Haskell 98 prin exemple - 109 - Dan Popa


Daca lista are doar un singur element, el fiind $i primul $i ultimul, a
returna lista tara ultimul element inseamna a returna lista vida. Daca
lista are insa cap $i coada, x $i xs respectiv, atunci capul este
pastrat iar de le este legata lista coada din care (recursiv) functia a
extras ultimul element. Observati ca aplicarea functiei este prioritara
fata de operatorul ":" .
A cincea functie este un predicat, null, care testeaza daca o lista
este vida:
null :: [a] -> Bool
null [] True
null ( :x False
Cap4Par2Ex5.hs

El returneaza True daca lista e vida. Altfel, daca lista are cap $i
coada, (coada poate fi vida 1), rezultatul este False.

A $asea functie est o veche cuno$tinta. operatorul de concatenare


pentru stringuri $i liste ++. A venit momentul sa vedem cum este
definit el in Standard Prelude:
Notati $i faptul c8 Ia definirea semnaturli noilor operatorl se folosesc
simboluri/e lor puse intre paranteze rotunda. Acest set de simboluri
pus in paranteze se comporta ca un nume obi$nuit de functie. lata
deci ca in Haskell o functie poate avea $i un nume format doar din
simbolurl. Mai inainte trebuie sa i se declare $i prioritatea $i
asociativitatea, altfel operatorul ++ nu poate fi folosit bine in propria
definitie.

0 introducere rn Haskell 98 prin example - 11 0 - Dan Popa


infix r 5 ++
(++) .. [a] -> [a] -> [a]
[ J ++ ys = ys
(x:x s ) ++ y s x : (x s ++ y s )
Cap4Par2Ex6.hs

Prin aceasta definitie recursiva afirmam doua lucruri: concatenarea


listei vide cu lista ys da chiar ys. Concatenarea listei (x:xs) cu ys ne
da o noua lista al carei cap este x, primul element din lista initiala, iar
coada este concatenarea celor doua liste xs $i ys ramase. Foarte
important este sa se indice corect sistemului care este prioritatea $i
felul cum asociaza operatorul nou introdus. Din acest motiv, lnainte
de func1ie este specificat explicit prin: infixr 5 ++ In lipsa caruia
sistemul n-ar putea analiza corect expresiile In care apare, in
anumite combinatii, operatorul ++. Prin aceasta declaratie ++ este
plasat pe nivelul de prioritate 5 fiind astfel mai putin prioritar dec~t

operatiile aritmetice obi$nuite dar mai prioritar dec~t cele logice.


Litera r indica modul de asociere Ia dreapta. Astfel concatenarea a
trei liste 11 ++ 12 ++ 13 ar trebui inteleasa ca 11 ++ (12 ++13).

A $aptea "minune" este map, eel at~t de familiar programatorilor


care au lucrat in Lisp. A$a cum am mai spus, map este functia care
aplica primul argument (care e o functie unara) pe r~nd, pe firecare
dintre elementele listei date ca al doilea argument. Returneaza lista
rezultatelor astfel obtinute... $i tunctioneaza $i pentru liste infinite I
Defin~ia sa este simpla (dar in Haskell nu e unica definitie
posibilal) :

0 introducere in Haskell 98 prin exemple - 111 - Dan Popa


map (a -> b ) -> [a ] -> [b]
map f xs f X I X <- xs
Cap4Par2Ex7.hs

'!1-· Ole /usr/sha•e/hu s~thiProlude hs- 'on ueror


Localion Edrt Y.trN~ ~o ~ookmarts Ioots SeiDngs yttndow !ielp

i-. ~¢ ~s o~ ~"~~ ~~~~ m


~ Lg_calion jiQ nle/usr/sharelhugslllh!Prelude hs •l'ij
~ -- Standard lis t functions {Prel udelis t} --- •

head :: [a] - > a


head (x: _) =X
las t :: [a] - > a
las t [x] =X
las t <_:xs ) = las t xs
tail :: [a] - > [ a)
tail <_ :xs ) = XS
ini t :: [a] - > [a]
init [x] = []
init (x:xs ) =x : init xs

null :: [a] - > Bool


null [] = True
null <_: _) = False
(++) :: [a] - > [a] - > [a)
[] ++ y s = ~s
(x:xs ) ++ y s =x : <xs ++ ys )

map :: (a - > b) - >(a ] - > [b)


map f xs = [ f X I X <- xs J •

4 •

Functlllmportante pentru prelucrarea llstelor, din blblloteca Prelude.

0 introducere in Haskell 98 prin example - 112 - Dan Popa


4.3. CASE fiiF in Haskell apol LET...IN ...

Pentru a introduce instructiunile case $i if , Paul, John $i Joseph


pleaca in discursullor de Ia un exemplu {pg. 17 din [Hud-99]), eel al
functiei take. in limba engleza "take" inseamna "a lua".

Pentru ca deja exista in Standard Prelude o functie take $i nu am


explicat inca felul cum se pot face programe modulare in Haskell
{ceea ce ar fi permis sa definim module de program care exporta
numai anumite functii sau nu exporta anumite functii !) vom numi
functia din exemplul de mai jos "take1".
lata functia pusa in discutie de cei trei autori:

takel I n t -> (a] -> (a]


takel 0 (l
takel [) (l
takel n (x:xs) = x : takel(n-1) xs

Ex1-take.hs

Take {ca $i take1 de mai sus) este functia care extrage lista primelor
n elemente dintr-o alta lista obtinand o alta lista. Functia este
polimorfica, poate prelucra orice tel de liste.
Editati acest program (puteti remarca $i faptul ca exista doua moduri
diferite de a scrie aceasta functie, inversand ordinea primelor doua
$abloane):

0 introducere Tn Haskell 98 prin exemple - 113 - Dan Pops


-- Ca 5ParSEx1 · laL.e hs. - KWnte
Ella E.dit j!ookmarks roots Settings J:ietp

Functia tak e
• o duri di fer i t e d e a o ae rie
i f si case
Dan Pop e 2 eprilie 2 005
d upe " A Gentle I nt rodu c t ion to Haskell 9 8 " PB 17

take 1 :: I nt -> [a] - > [ e ]


t ake 1 0 _ = []
t ak e1 [J = [J
tak e 1 n ( h: t ) =h : t a k e 1 {n-1 ) t
t a ke2 :: I nt - > [a] - > [a]
t a k e2 [] = []
t a ke 2 0 _ = (]
t a k e2 n (h : t) = h: take 2 (n- 1) t

Functia take, modurl diferite de a o scrie, ordinea ecuatlllor difera

Testati felul cum extrag functiile de mai sus primele n elemente dintr-
o lista:
• D
File Seulona Semngs M'elp

Type :? f or he l p
Main> t ake1 0 [1.2.3]
[J
Mai n) t ake 1 1 [1 . 2 . 3]
[1]
Mai n ) take1 2 [1 . 2 . 3J
[1.21
Main> take1 3 (1.2 . 3)
[1.2.31
Mai n ) take2 1 ['a ' . ' c'. 'k']
··a ..
Main> t ake2 2 ['a' . ' c'. 'k']
··ec"

D.lllr)J
Listele de caractere sunt considerate String-uri.

lar functiile de mai sus pot fi folosite $i pentru a extrage prefixe dintr-
un string, stringurile fiind de fapt liste de caractere.

0 introducere in Haskell 98 prin exemple - 114 - Dan Popa


• •t • I • t I * I . I • • • [_;1~
File Sessions Selllngs Help

Main> take1 7 "autorul e Da n Popa"~


"autorul"
Main> take1 4 "Popa Dan "
"Popa"
Main> take2 3 "Dan Popa"
"Dan"
Main> I

Extragerea de prefixe din String-uri, cu aceleafl func;til.

Pentru tratarea distinct~ a alternativelor, lnafar~ de ecuatii


succesive, limbajul Haskell pune Ia dispozitia programatorilor ~i o
instructiune case. Veti lntreba In ce situatii este mai avantajoas~

dec~U pattern-matching-ul sau pattern-matching-ul cu gard~ . Un


r~spuns (nu e singurul} ar fi: puteti programa structural scriind un
case in lnteriorul altul case. A~a ceva era ceva mai greu de
realizat cu pattern-matching sau cu g~rzi. Functia de mai lnainte se
poate rescrie ca mai jos. Transformarea este imediata:

ta ke l .. Int -> [a] -> [a J


takel n 1 = case n, l of
0, -> [J
, [] ) -> [ l
n , h : t) -> h : takel (n-1) t

Ex2.hs

Editati acest program cu editorul dumneavoastra favorit apoi rulati-L

0 introducers fn Haskell 98 prin example - 115 - Dan Popa


~-- Cap5Pat5Ex2 hs - KW11Ie
file J;dlt aookmask' Iools ~ettings J:ielp

0 5/it O ~ !il) ~ X Q) ~ ~ ~ ~
-- Func tia t ake
-- • oduri diferite de a o s crie
-- cu cas e
-- Da n Popa 2 apr ilie 2005
-- dupa "A Gentle Introduction t o Has kell 98" pg 17
I
take1 : : Int - >[a] - > (a]
take1 n 1 = case (n.l> of
(0 . _) - > []
L .[J> - > [ ]
(n.h : t ) - > h : t a ke1 (n- 1 ) t

Line 6 Cot 1 INS

Ex2.hs - atlfat Tn fereastra edltorulul

Ca ~i varianta din exemplul precedent ~i aceasta este capabila sa


extraga prefixul dintr-un string:
r:J-• dana>RonP.on 'home/dan/ racbca-haslei Kon1ole- Kcnsole
File Seulons Se!lings Help

/usr /s hare/hugs/lib/Prel ude .hs • :


Ex2 .hs
T~:~pe :? for help
Main> take1 4 '" Popa V. Dan'"
"Popa"
Main> take1 7 '"Popa V. Dan'"
'"Popa V. ··
Main) I
D.lrfrll
Ex2.hs - rularea programulul

0 forma foarte des folosita de case este aceea i n care expresia


dupa a carei cazuri se face alegerea se evalueaza Ia o valoare
logica, True sau False, iar acestea sunt singurele cazuri. 0
asemenea alegere este de fapt un If.

Este deci mai simplu ca. intr-un asemenea caz, in loc sa scrieti un

0 introducere in Haskell 98 prin example - 116 - DanPopa


case ca acesta:
case <exp1> of True -> <exp2>
False -> <exp3>
sa folositi un if a carui sintaxa este exact ca aceea a unui If din
Pascal:
If <exp1 > then <exp2> else <exp3>
Facem observatia ca acest if ...then ...else seamana ca mod de
utilizare cu operatorul "semn de intrebare - doua puncte" (... ? ...
: ... ) din limbajul C, operatorul ternar cu care se scriu expresiile cu
alternative.
Un exemplu tipic, dat In mai toate manualele de limbaje de
programare atunci cand e vorba de instructiunea if, este programul
care rezolva ecuatia de gradul al doilea. Exemplul urmator mai
ilustreaza folosirea unui tel de variabile locale introduse cu
substitutia let ... in ... precum ~i extragerea radicalului cu functia
sqrt:
Ecuatia de gradul al !I-lea
Exemplu de utilizarea a lui if
Rezultatele prezentate ca o lista
rezolv [Float] - > [Float]
rezolv (a : b : c : []) =

let delta = (b*b-4*a*c)


in
if delta < 0
then []
else if delta 0

0 introducere in Haskell 98 prin exemple - 117 - Dan Popa


then [ (-b/ (2*a) ) ]
else (-b+sqrt(delta) ) /(2*a) ,
(-b-sqrt(delta))/(2* a )]
rezolv []
Ex3.hs- ecua1ia de gradul al doilea (cu Jlste)

Not~ despre comparatii, egalitate :;;i "atribuire" (am scris-o cu


ghilimele deoarece let..in... din limbajele functionale nu este
propriuzis o atribuire, dar o vom mai numi totu:;;i a:;;a pentru cititorii
obi:;;nuiti cu limbajele imperative ) :

in priviinta felului cum se scriu operatorii pentru comparatii :;;i


"atribuirea" , Haskell seam~n~ Ia sintax~ mai mult cu C-ui decat cu
Pascalul, de data aceasta. Egalitatea se scrie cu un dublu egal iar
"atribuirea" cu unul singur. Atentie Ins~ Ia operatorul "diferit". A:;;a
cum ii st~ bine unui operator cu asemnea nume, felul cum se scrie el
este diferit de Ia un limbaj Ia altul. Diferit-ul din Haskell (/=) nu se
scrie Ia fel cu eel din C (!=) sau Pascal(<>).

Despre let...ln ... Aceast~ constructie sintactic~ nu este o instructiune


de atribuire ca in limbajele imperative ci o expresie. Valoarea ei este
dat~ de expresia de dup~ in in care variabilele libere au fest inlocuite
cu valorile lor date intre let :;;i in. in lipsa lui let ... in.. programul care
rezolv~ ecuatia de gradul al doilea ar fi ar~tat ca in imaginea de mai
jos.

0 introducere in Haskell 98 prin example - 118 - DanPopa


~
[lie f.dH llool:marks Ioolc SeiiJngs l:lelp

Cl6Q O c:9 ~ ~ X Q:!&l~ ~~ ~


Ecuetie d e gra dul a l d oil e e
.. . - .
-- De n Pope 2 aprilie 200~
-- re zolvarea ec de g r e l doilee . fer a "l e t " .. = ... " in"

r e zolv .. [Flo at] -> [Float]


r ezolv (a : b : c: []) = if (b•b - 4 " a • c ) < 0
the n [ ]
el s e
i f (b•b - 4 " a • c > == 0
the n [ <- bt <2• a» )
else [ ( - b + s qrt((b•b - 4 .. 8 • c »)
/( 2 .,o >
<- b - s qrt((b•b - 4 .. 8 •c» >
/( 2• a >
)
~
r e zolv _
4
= []
. ...... 1., ·14 •
..
L~e 2 Col 27 ~Ct.

Ex3.hs - rn curs de edltare


-
Despre utilizarea practicA a lui LET. ..IN ... : DacA se scriu mai multe
initializAri pentru variabilele locale dar nu pe linii succesive ca mai jos
let
b=1
c=2
in b + c
ci pe aceea$i linie, ele trebuie sa fie despArtite prin ";" a$a ca vor
arata ca mai jos:
let b = 1 ; c = 2 in
b+c

Acest programelor de mai sus li se pot aduce $i lmbunatatiri.


Privindu-le cu atentie observAm, printre altele ca:
(2*a) se repeta inutil
sqrt(delta) se repetA inutil

0 introducere in Haskell 98 prin example - 119 - Dan Popa


- rezultatele sunt prezentate ca o lista, se poate si altfel, de exemplu ca un
tip reuniune, declarat cu declaratia data:
-- Ecuatia de gradul a l !I-le a
{-- Exemplu de utilizarea a lui if . Rezultatele sunt
prezentate ca o structura de date dintr-un tip reuniune
cu 3 variante . --}

data Rezultat Solutia Un ica Float


Nici o solutie
Doua Solutii (Fl oat , Float)
deriv ing Show
rezolv [Float) - > Rezultat
re zol v (a : b : c : [ ] ) =
let delta = (b *b-4*a*c )
da 2 * a
s sqrt (delta )
in
if delta < 0
then Nici o solutie
else if delta == 0
then Solutia Unica (-b / (da))
else Doua Solutii

(-b+s) /da ,
(-b-s)/da

Ex3.hs- ecuatJa de gradul al doilea ( tipuri utillzator, let.. ln ... mal blne foloslt)

0 introducers fn Haskell 98 prin example - 120 - Dan Popa


Puneti sistemul Haskell sa rezolve ecuatiile, dupa ce ati incarcat
ultimul program, tast~nd de exemplu:
Main> rezolv [1 , 2 , 1]
Solutia_ Unica (-1 . 0)
Main> rezolv [1 , 2 , 4]
Nici o solutie
Main> rezolv [1 , 2 , 0]
Doua Solutii (0.0 , -2 . 0 )
Main>
$i inca ceva. Atunci c~nd let ... in ... define$te valorile mai poate sa
le extraga din interiorul unor structuri conform unor $abloane $i mai
mult chiar - suntem intr-un limbaj functional - poate sa lucreze cu
functii, inclusiv oferindu-le alte nume, temporare. lncercati:
Main> let (a , b) = (1 , 2+3) in a+b
6
Main> let (+)=(*) in 2+5
10
De$i ar fi amuzant, nu recomandam totu$i sa programati in acest din
urma stil deoarece poate face programele mai greu de inteles pentru
cititori.

4.4. ~I ... WHERE .. . , un fel de LET... IN ... scris invers

in Haskell, putem scrie calcule cum sunt cele din programul care
rezolva ecuatia de gradul al doilea $i concentr~ndu-ne asupra
programului $i abia apoi asupra formulelor subexpresiilor care se

0 introducere in Haskell 98 prin exemple - 121 - Dan Popa


repet~ . cum este cazullui delta sau allui 2*a. Ceea ce in programele
scrise cu LET ... IN .. . se scria Ia dup~ in acum se scrie Ia inceput.
lar ce se scria intra let ~i in acum se scrie dup~ un nou cuvAnt cheie,
WHERE.
Ecuatia de gradul al Il -lea
Exemplu de utilizarea a lui if ... then ... else...
Rezultatele sunt prezentate ca o
structura de date dintr-un tip reuniune
Valorile expresiilor comune sunt la final .

data Rezultat Sol u tia Unica Float


Nici o solu t ie
Doua Soluti i ( Float , Float )
de riv ing Show

rezolv [ Float ] -> Rezultat


rezolv (a : b : c : [ ) )
if delta < 0
then Nici o solutie
else if delta == 0
then Solutia Unica (-b/(da))
else Doua Solutii

(-b+s)/da ,
( - b - s)/da

where

0 introducere fn Haskell 98 prln example - 122 - Dan Popa


delta = (b*b-4*a*c)
da = 2 * a
s = sqrt(delta)
Ex3.hs- ecuatJa de gradul al dollea ( tipurt utlllz:ator, ...where ... este foloslt)

0 introducere Fn Haskell 98 prin exemple - 123 - Dan Popa


lmplementarea unui limbaj in Haskell

Despre: Realizarea unui interpreter (mai exact back-end-ul) fn


limbajul Haskell 98. Cu aceasta ocazie trecem fn revista structurile
de date necesare interpretarii sintaxei abstracte a unui limbaj $i
memorarii valorilor variabilelor, cateva instructiuni de selectie $i
schema genera/a a unui interpreter.

Vom observa ca In Haskell se pot scrie cu u~urinta chiar


interpretoare modulare, adaptabile, lucru care va deveni evident
atunci cand vem prezenta cempenentele unui interpreter ~i

diagrama acestuia. Existenta In biblieteca Standard a claselor


menadice permite o separare clara a descrieriler sintaxei abstracte
de cele ale semanticii. Ca urmare interpreterul poate fi adaptat fearte
user. La aceasta u~urinta centribuie esentiallnsu~i limbajul Haskell.
Din aceasta cauza, programele scrise In Haskell 98 sunt in medie de
9-25 de eri mai scurte (cf unei intreduceri de pe site-ul
www.haskell.org). afirmatie Ia care am subscris. Simplitatea

0 introducers fn Haskell 98 prin example - 124 - Dan Popa


aparent~ a acestui capitol se datoreaz~ expresivit~tii Haskell-ului.

5.1. Sintaxa (abstracti)


0 introducere pentru cititorii care n-au auzit de gramatici $i ierarhia
Chomsky.

Ce este practic sintaxa unui limbaj de programare ? 0 suita de


descrieri (formale sau nu) ale felului cum arat~ . cum se scriu
diversele constructii sintactice (gramaticale, cu alte cuvinte) in acel
limbaj. Uzual se definesc printre altele moduri de a scrie :
Variabilele, Constantele, Termii, Expresiile, lnstructiunile simple,
lnstructiunile structurate, Declaratiile etc. Pentru fiecare din
constructiile sintactice de mai sus se dau specificatii conform teoriei
gramaticilor formale, folosind o gramatic~ independent~ de context
(GIC) sau o notatie echivalent~ (BNF, EBNF ori variante ale lor).
Pentru fiecare categorie se pot da mai multe reguli de gramatic~ iar
notatiile moderne permit adesea comasarea lor intr-o regul~

comun~. De exemplu:

Term= var I const I Term"+" Term

Regula de mai sus afirm~ c~ un term poate fi i n cazurile extreme (ca


simplitate) o variabila sau o constanta iar in rest o suma de doi termi.
Notatia este asemanatoare cu notatia EBNF propus~ de Wirth. (0
descriere formala a EBNF chiar in EBNF se poate g~si in cartea
prof. P.D.Terry, Compilers and Compilers Generators, Rhodes

0 introducere in Haskell 98 prin exemple - 125 - Dan Popa


University, 1996 In subcapitolul 5.9.1, accesibila $i pe Internet). in
exemplul de mai sus avem o descriere de sintaxa concreta,
neabstracta. Pe de alta parte sintaxa abstracta (cea In care e
reprezentat programul dupa analiza sintactica) este mai simpla. in
practica puteti pastra un terminal din regula care va fi "promovar pe
nivelul precedent al arborelui, ca nod parinte $i totodata simbol
functional cu ocazia trecerii de Ia arborele sintactic Ia eel operatorial.
Exemplu:

Arborele slntactlc al unul expresll care este o sumi a dol terml

Arborele sintactic de mai sus se transforma in arborele operatorial


din figura:

Arborele operatorlal al unul expresll care este o sumi a dol terml

lar valorile expresiilor transformate in asemena arbori se pot calcula

0 introducere Tn Haskell 98 prin exemple - 126 - Dan Popa


cu un interpreter (o procedura) recursiva, care evalueaza lntAi
subarborii apoi arborele mare pe baza valorilor subarborilor.
Aceste structuri arborescente sunt foarte utile Ia realizarea
interpretoarelor. In Haskell ele vor fi exprimate prin declaratii data.
Exemplu: Pentru un limbaj simplu, care contine expresii formate cu
constante, identificatori de variabile $i adunari de termi, declaratia va
lncepe a$a:

data Term Con


Id
Add

Urmeaza sa completam declaratia Haskell de mai sus. Pentru


adunare este simplu, se aduna un Term cu un Term:

data Term Con


Id
Add Term Term

Constanta - o vom considera numar lntreg - $i o declaram de tip Int.


(lnt ca $i Integer desemnaza In Haskell multi mea numerelor intregi $i
este un tip deja definit.)

data Term Con Int


Id
Add Term Term

0 introducere in Haskell 98 prin example - 127 - DanPopa


Numele unei variabile este un String. Daca String vi se pare prea
general, prea putin sugestiv, li puteti gasi un alias, cum ar fi Name
(in engl. Tnseamna chiar nume), alias care se declara cu type.

type Name String

data Term Con Int


Var Name
Add Term Term

(Nota: Pentru cazul cAnd se interpreteaza un limbaj functional,


trebuie sa avern prevazut ~i cazul cAnd un Term poate fi o lambda
expresie ce va servi in final Ia o aplicare.)
In final, Termii evaluati de interpretorul pe care il vom scrie mai
departe vor fi declarati in Haskell 98 astfel:

type Name = String


data Exp Num In t
Add Exp Exp
Id Identifier
With Identifier Exp Exp

0 introducere In Haskell 98 prin exemple - 128 - DanPopa


5.1.1 Un mic interpretor

Unor termi ca celor de mai sus li se pot atribui rapid valori dintr-o
mul- time fixat~ cum este cea a intregilor, calculand valorile din
mers. Programul care face acest lucru se nume~te interpreter. Un
asemenea mic interpreter, realizat in manier~ clasic~. neextensibil~.

am g~sit in volumul Programming Languages: Application and


Interpretation, de Shriram Krishnamurthi de Ia Brown University,
editia din 8 decembrie 2004, disponibil pe Internet. Veti recunoa~te

in exemplu acela~i tel de expresii de evaluat chiar dac~ s-a folosit


Num in loc de Con sau Identifier in loc de Name in scrierea lor.
Exemplul de mai jos este inspirat din eel aflat Ia paginile 102-106 din
volumul amintit, pagini din capitolul 10.1.5.
incepem programul cu cateva preparative:
Deoarece in biblioteca standard exist~ o functie lookup $i autorul a
folosit acest nume pentru una dintre functiile sale vom preciza c~

import~m biblioteca standard dar ascunzandu-1 pe lookup.

import Prelude hiding (lookup)

Not~ : Puteti s~ v~ convingeti de existenta unei functii lookup


inc~rcate de interpreter din biblioteca standard cerand cu comanda :
t afi$area tipului acesteia:

0 introducere fn Haskell 98 prin exemple - 129 - Dan Popa


Prelude> : t lookup
lookup :: Eq a=> a-> [ (a , b ) ] -> Ma ybe b

dar nu vom explica Inca o asemena schema de tip deoarece unele


notiuni referitoare Ia ipotezele in care exista acest tip $i Ia valorile
finale ale functiei (valori din ceea ce se nume$te Monada Maybe) nu
au fost explicate inca.

incepem textul sursa cu c~teva sinonimii de tipuri:

type Identifier String


type Value Int

Se observa ca multimea de valori ale rezultatelor evaluarii este


restr~nsa Ia numere intregi. Urmeaza apoi o serie de doua declaratii
mai importante:

type Env [(Identifier,Value)]


data Exp Num Int
Add Exp Exp
Id Identifier
With Identifier Exp Exp

intai am definit un tip lista de perechi. Fiecare pereche e formata din


identificator $i valoarea sa asociata. Acest tip este de obicei numit

0 introducere in Haskell 98 prin exemple - 130 - Dan Popa


Environment (inseamna mediu inconjurator). Urmeaza declaratia
unui tip nou introdus, definit recursiv (ca arborii) care foloseGte 4
constructori de date (corespunzatori a 4 feluri de noduri i n arbore).
AceGtia sunt Num, Add, ld Gi With. Sunt arborii (cu patru feluri de
noduri din care doua feluri interioare doua exterioare) asociati
expresiilor de evaluat.
0 expresie de evaluat poate fi :
- un Num adica un integ dat prin valoarea sa n
- un ld adica un identificator al unei variabile dat sub forma string-
ului de dupa ld
- o adunare (Add ... ) a doua expresii , adica o expresie compusa,
suma sintactica de subexpresii
- o expresie (With ... ) care (banuim deja), se va calcula inlocuind in
a doua sa subexpresie valoarea identificatorului indicat cu valoarea
primei subexpresii.

lnterpretorul este definit ca o functie de Ia Expresii $i Environment


(context) p~na Ia Valeri, scrisa in stilul curried cu care deja, in
Haskell, ne-am obi$nuit:
interp :: Exp -> Env ->Value
interp (Num n) e = n

interp (Add stg dr) e = interp stg e + in terp dr e


i nterp (Id i ) e = loo kup i e
i nterp (With id leg exp_asoc exp_princ ) e =
interp exp_princ
(extend e id_l eg (interp exp_asoc e))

0 introducere in Haskell 98 prin exemple • 131 - Dan Popa


Este clar felul cum e definitA interpretarea: Pentru un numAr n,
valoarea sa este n in orice context e ar fi ea plasatA.
lnterpretarea unei sume intr-un context e este suma rezultatelor
interpretarilor subexpresiilor stg ~i dr in acel context e. Se observA
ca expresiile nu produc efecte laterale (side effects) prin urmare
contextul de evaluare a celei de-a doua expresii este Ia tel ca al
primeia. Totu~i . dacA vom dori sA simuiAm un context care sA-~i
schimbe starea ~i sA transmita aceastA stare schimbatA, o solutie ar
fi recurgerea Ia o monada, monada starilor .

lnterpretarea, evaluarea, unui identificator presupune cautarea lui in


Environment folosind functia de cAutare lookup pe care o vom
cementa in continuare. Rolul ei este sA returneze valoarea asociatA
identificatorului dat in contextul existent, e.

In cursul experimentelor noastre am scris functia lookup altfel decat


autorul mentionat anterior, dar cu rezultate comparabile.

lookup Identifier -> Env -> Value


lookup x [ l 0
lookup x ((y,v) : e)= if x y then v
else lookup x e
Daca x este cautat in lista vida se returneaza zero. (De notat ca nu
putem deosebi acest zero de valoarea zero, lucru pe care, aticipam,
funqia lookup din Prelude, realizatA cu o monadA, monada Maybe,

0 introducere in Haskell 98 prin exemple - 132 - DanPopa


11 poate face.) Functia noastra lookup da interpretorului un
comportament de altfel familiar: variabilele neinitializate explicit in
context au implicit valoarea zero $i pot fi folosite ca atare.

If ... then ... else... (de$i va fi explicat pe larg abia in capitolul 5.5)
constatam ca functioneaza exact ca in alte limbaje de programare
din familia C-ului. Acolo exista un operator ternar similar (notat cu
? :) care avea acela$i efect: daca expresia logica inifia/a era evaluata
Ia true se retuma valoarea urmatoarei expresii (aici cea de dupll
then) altfel se returna va/oarea celei de-a treia expresii cea de
dup~ else.
Sa nu uitam functia extend al carei rol este de a extinde contextul cu
variabila $i valoarea introduse suplimentar, de (obicei de) un With.

extend : : Env -> I denti fier -> Val ue -> Env


extend e i v = ( i ,v ) : e
lata efectul functiei extend aplicata de doua ori succesiv listei vide:
Q-1- dan•g>RonRon ro 'home·dan'_r-1atenaleTe:a'6_112_plus_,

Session Edit View Bookmar1cs Settings Help

Main> extend [] • a • 1 ...


I ("a· , 1) 1
Main> extend (extend (] ·a• 1) "b" 2
[("b" ,2), ("a" , l )]
Main> I
ro ~Shell

Extlnderea contextulul cu o varlablli b de valoare 2 foloslnd funqla extend.

intregul interpreter arata cain imaginea urmatoare:

0 introducere in Haskell 98 prin exemple - 133 - Dan Popa


f i le ,lid1t Y,lew jlookmaru Iools ,S.ettlnos l:lelp

G ~ a r.J Q ~ • c.t • L:\ ~~ ~ ~


bpor t Prelude htding (l ookup )
0~\..~ddt l l JrtJptJIJ .tnCifll•'r-J'
type Ident1f1er = Str1n9
type Value = lnt
£tr?,~J'.ltJ..~L~ ae Clp Jlt:" (J:,t~- Env..J.ron.t"Pflt' Sl
~lP P)tpt•illlOr Of' J'1terpr-Ptdt
type Env = ((ldenttfier.Valuell
data Exp = Nu. Int
I Add Exp Exp
I Id Identifier
1 Wtth Ident1f1er Exp Exp
I 'rrsrpt P. ~ C" l l"J,.+ nIt r if c; f, rr~
1nterp Exp -> Env -> value
interp ( Nu. n) e n =
inter p (Add stg dr l e = interp stg e ~ ~nterp dr e
interp Cid il e = lookup ~ e
1ntcrp (W>th id_ teg exp_asoc exp_prtn c ) e =
1nterp exp_princ
(extend e 1d_ leg (tnterp exp_ aso c el)
rui"•tr 1 1~ .a '"1' t e 'ooJ.t1p. e·r~'f1
t oo kup : : I dent 1 fl.er - > Env - > Va t ue
l ookup >< II E)
l ookup x ((y,v) : e) i f x == y then v
els • 1 ookull x e

~>l<t<>nd · Env -> Ident1her ->value -> Env


extend e i v = Ci , vl : e

ILine; 20 Col; 24 IN S NORM

Testarea interpretorului decurge caIn aceasta imagine:


Q-a.. dantOlRonRon 10 lho n1eo/~..jan.'_r...1atertaleT~za::t;_h2_plus_ln [!](g)~
Session Edit View Bookmarks Settings Help

!'lain> interp ( Add ( Nu m 3) (Num 4)) [ 1


7
Main> interp ( With ·x· (Add (Num 1 ) ( Nu m 10)
) (Add ( Id "x") ( Id "x"))) [ 1
22
Main>
Main> I
sPI ,•J Shell

lar rezultatele dovedesc ca ambele expresii au fost evaluate corect.

Dezavantajele unui asemenea interpretor:


- Este greu de introdus mesajele de eroare, nu avem o structura de

0 introducere in Haskell 98 prin exemple - 134 - DanPopa


date pentru valorile retumate, capabila sa reprezinte sau o valoare
sau un mesaj de eroare. Problema exista ~i pentru alte feluri de
erori, decat cele de calcul.
- Este greu de extins atat setul de date initiale cat, mai ales
multimea de valori finale. (Fara modificari majora, interpretorul de
mai sus va returna doar rezultate dintr-un tip numeric, nu neaparat
lnt).
- Nu ofera facilitati pentru afi~area unor valori dintr-un tip
nestandard, definit de utilizator, chiar daca semantica acelui tip ar
fi u~or implementabila. (E "Problema extensibilitatii printerului".).
- Nu ofera facilitati pentru introducerea (parsarea) datelor din tipuri
nou definite.
- Chiar simpla l ncercare de a i nlocui semantica existenta cu una cu
care prezinta analogii (de exemplu calculele cu lntregi cu calcule
cu clase de resturi) nu este banala deoarece nu exista nici un mod
de a separa o descriere semantica ce afirma ca "se aduna ...
cu ...." de descrierea felului cum se produc ~i se succed
asemenea calcule.
In concluzie un asemenea interpreter nu este u~or extensibil, nu este
u~or adaptabil. Chiar ~i simpla extindere a multimii de valori pune
problema, motiv pentru care II vom abandona In favoarea unor
interpretoare mai complexe dar mai flexibile. lata de ce e nevoie sa
mai ~tim pentru a Iuera Ia proiectarea lor:

0 introducere Tn Haskell 98 prin exemple - 135 - DanPopa


5.2. Cum exprlmam valorlle rezultate ?

~tim deja c~ un limbaj interpretat este un limbaj pentru care se


furnizeaz~ un evaluator (interactiv) numit interpreter. Afirm~m
aceasta deoarece notiunea de evaluare se poate extinde natural de
Ia expresii pAn~ Ia programe complexe. A rula un program inseamn~
pan~ Ia urm~ a-1 evalua $i a vedea ce rezultat retumeaz~ ($i
eventual ce efecte laterale sub form~ de operatii de 10 produce).
lnterpretorul incearc~ s~ atribuie valori ($i tipuri, deoarece un limbaj
poate fi tipizat) expresiilor primite spre evaluare. Aceste expresii sunt
scrise folosind diversele structuri sintactice ale limbajului (ex:
adunarea termilor de mai sus) ~~ pot da ca rezultat valori din diverse
tipuri (Integer, Bool, Float, Char sau tipuri compuse). Uneori expresiile nu
dau valori rezultat, de obicei din cauza unor argumente incorecte (ex: 7 I 0
sau operatla de extragere a primului element dintr-o lista vida.)
in limbajul functional pe care 1'1 dam In continuare ca exemplu (realizat
dupa lucrarile profesorului P. Wadler) interpretorul va da trei tipuri de
rezultate: intregll, functllle ~~ rezultatul nedefinit, gre~it (Wrong).
Pentru a descrie tipul reuniune al acestor valori o noua declara~e va
trebul sa fie adaugata In programul interpretorului:

data Value Wrong


Num Int
Fun (Value -> M Value)

Explicatiile de dat in ceea ceo prive$te ar fi urmatoarele:


Wrong este un constructor de date (ales de programator) ca $i Num

0 introducere In Haskell 98 prin example - 136 - Dan Popa


$i Fun. Fiecare din ace$ti trei constructori corespunde unui subtip al
tipului Value. Value este un tip reuniune. M Value este o valoare din
monada M, un tel de capsul~ care contine un Value, care descrie un
calcul ce va produce un element de tip Value. Din motive care tin de
modularizarea interpretorului $i separarea sintaxei de semantic~.

este utilizat~ monada M iar valorile rezultate sunt elemente din


monada M. Asupra monadei M vom reveni intr-un subcapitol ulterior.
Deocamdat~ considerati-1 pe M doar un constructor de tip care
transform~ elementele tipului reuniune Value In cele din monada M
Value. Semantic vorbind, monada este o structura algebrica a c~rei

elemente sunt procese de calcul capabile s~ se lnl~ntuie, s~ se


succead~. Este lnzestrata cu operatiile >>= (cite$te bind) $i return
care fac aceste lnl~ntuiri de calcule $i respectiv, produc Valori
lncapsulate. Algebri$tii ar scrie (M a, >>=, return).

Care va fi semnificatia unei functii anonime, a unei lambda expresii,


intr-un limbaj functional ?
R~spuns: Un proces de calcul care depinde (uneori) de valoarea
argumentului initial (argumentul care i se d~) pentru a da ca rezultat
o alt~ valoare. Practic se transform~ o valoare intr-un proces de
calcul capabil s~ dea ca rezultat o aM valoare. in momentul cand
am scris acest rand m-am oprit lntrebandu-m~ de ce s-a folosit M ca
notatie pentru aceast~ multime de Procese de calcul inzestrat~ cu
operatii care este monada $i nu P. Dar r~spunsul era imediat $i
evident: Atat termenul Monad~ cat $i numele inventatorului acestui
concept, profesorul Moggi (E.Moggi) incep cu litera M.

0 introducere in Haskell 98 prin example - 137 - Dan Popa


5.3. Cum reprezentam memoria sau asocierea variabile-valori

De obicei, in interiorul interpretorului unui limbaj de programare,


calculul valorii unui term se face pornind de Ia valorile variabilelor
dintr-o memoria sau un a$a-zis Environment (in eng. inseamna
mediu inconjurator) sau context semantic. Acesta poate fi
reprezentat $i ca o lista de perechi variabila-valoare, cum se
intampla Ia un interpreter clasic, eel allimbajului LISP.
Sa luam spre lamurire un mic exemplu: in contextul definit de lista de
perechi x=S $i y=7,expresia z=x+y va avea valoarea 13 iar in
contextul x=S $i y=7,expresia z=x+y va avea valoarea 3.
Atunci cand scriem un interpreter sau un prototip de interpreter in
Haskell, contextul se reprezinta eel mai U$Or printr-o lista (similara cu
acea din LISP) care asociaza in perechi numele (din tipul Name) cu
valorile (din tipul Value).
Cum in Haskell exista constructori de date $i tipuri gata definiti
pentru liste ( [,] - paranteza patrata) $i pentru perechi ( ( ,) -
parantezele rotunda cu o virgula) este suficient sa scriem tipul lista
de asociere ca: [( Name, Value)) $i sa-i dam un nume, un alias,
folosind o declaratie type:

type Environment [ ( Name , Value) J

Va puteti imagina, daca doriti, aceasta lista de asociere ca un fel de


pod de trecere i ntra doua maluri, malul dintai fiind al termilor forma~

0 introducere in Haskell 98 prin example - 138 - Dan Popa


folosind variabile ~i malul al doilea fiind al valorilor acestor termi ,
valori calculate pornind de Ia valorile acestor variabile. Observati c~

pentru termmii compu~i valoarea nu este dat~ direct ca pentru


variabile ci este definit~ prin inductie structurat~ ~i calculat~ de
functia de evaluare a interpretorului. (Cititorii interesati de aspectele
matematice implicate se pot apleca printre altele asupra notiunii de
catamorfism din teoria categoriilor... care nu face obiectul acestui
subcapitol.)

0 intrebare se poate pune aici: 0 alt~ valoare definit~ inductiv n-ar


trebui s~ fie stringul (textul) care e tip~rit de interpreter atunci cand ii
afi$eaz~ omului de Ia calculator rezultatul unui calcul ?
R~spunsul este afirmativ. lar in setul de example oferit de Hudak,
Peterson ~i Fasel ca supliment online al c~rtii "A Gentle Introduction
to Haskell" exemplul num~rul 5 ilustreaz~ o alt~ facilitate a limbajului
Haskell 98: Puteti determina interpretorul ca odat~ cu tipul de date
definit recursiv s~ construiasca $i functia recursiv~ Show asociat~.

necesar~ interpretorului pentru afi~area valorii din tipul respectiv.


Pentru a obtine acest efect amintim c~ este suficient s~ ad~ugati

dup~ declaratia tipului sintagma "deriving Show". De exemplu


declaratia:
data Tree L eaf a I Branch (Tree a) (Tree a)

devine
data Tree = Leaf a Branch (Tree a) (Tree a)
deriving Show

0 introducere In Haskell 98 prin example - 139 - Dan Popa


lar din acest moment nu trebuie sa va mai faceti griji daca o expresie
de evaluat intoarce o valoare de tip Tree, deoarece interpretorul va
$ti s-o afi~eze .

5.4. Compunerea valorilor,evaluarea termilor compu,l, pas ,1


proces de calcul

incepem acest subcapitol cu o intrebare: Ce rezulta in urma evaluarii


unei expresii, intr-un limbaj functional ? 0 valoare ? Nu I Un proces
de calcul. Adica ceva care e capabil sa inceapa un calcul (pornind
de Ia o valoare initials, eventual) $i e capabil sa dea ca raspuns o
valoare finals. intrucat aceste procese de calcul pot fi succesive,
legate in cascada (de exemplu unul prime~te ca data initials ce a
calculat precedentul), multimea acestor procese de calcul trebuie sa
poata fi inzestrata cu operatia de a extrage valoarea produsa de un
proces apoi de a-i aplica o functie (care poate produce alt proces) $i
de a combina rezultatele.
Pe de alta parte, intrucat orice valoare constants dintr-un tip
oarecare a poate fi asociata cu un proces de calcul (vid) care da
exact acea valoare, trebuie sa existe ~i un mod banal de a
transforma valoarea constants din tipul a in procesul de calcul
corespunzator, care produce acel a. in final vom putea realiza
lanturi, inlantuiri de procese de calcul care pornesc de Ia o constants
initials $i ajung Ia o valoare finals, dupa cum dorim.

$i deja intuim ca multimea proceselor de calcul formeaza o structura

l
0 introducers in Haskell 98 prin example - 140 - DanPopa
algebrica inzestratA cu propriile ei operatii specifice. Este momentul
in care realizam ca avem nevoie de o teorie algebrica a
interactiunilor dintre calcule. (Ea i~i va arAta utilitatea ~i Ia simularea
intr-un limbaj functional a programarii imperative, acea programare
realizata prin comandarea de calcule succesive computerului). Aici
intra In scena ni~te multimi de calcule numite monade (cu singularul
monad§).

Ele rezolva ~i problema ordinii operatiilor In limbajele functionale. Ea


consta In dificultatea de a programa succesiuni de calcule In limbaje
functionale. De ce e dificil ? Semantica limbajelor functionale este
definita lntr-un mod in care ordinea evaluarii argumentelor unei
functii cu mai multe argumente poate fi una oarecare din ordinile
posibile. Este normal pentru functii, dar ia lncercati sa programati
imperativ In asemenea conditii, cand nu ~titi dintre doua expresii
care este evaluata prima !?!

Ori problema ordinii evaluarilor se pune acut in multe cazuri, inclusiv


In cazul operatiilor de 1/0. De exemplu, lntr-un program cu operatii
de 1/0 este obligatoriu ca mai lntai sa se afi~eze mesajul explicativ
"Dati valoarea lui x " ~i abia apoi sa se citeasca valoarea lui x.
Din toate aceste incurcaturi ne va scoate monada.

Totu~i , lnainte de a o prezenta mai sunt cateva notiuni (aflate In


legatura cu limbajul Haskell} pe care trebuie sale explicam:

0 introducere in Haskell 98 prin example - 141 - DanPopa


5.5. Semantica 'i interpretare

Amintiti-va cum ati invatat numerele romane. Vi s-au explicat int~i

semnificatiile elementelor celor mai simple I, V, X, L, C, M


(1 ,5, 10,50, 100,1 000) apoi regulile - destul de sofisticate - pentru
combinarea lor si aflarea valorii unui numar compus.
Exact Ia tel se petrec lucrurile atunci cand scriem un interpreter.
lnterpretarea unui limbaj presupune stabilirea, printre altele, a trei
elemente. Practic, atunci c~nd inventam, definim, un interpreter va
trebui sa explicam computerului trei feluri de lucruri, sa-i raspundem
Ia trei intrebari (desi vom vedea in capitolul noua ca pentru unele din
aceste intrebari exista raspunsuri universal valabile, justificate
matematic):
a) Cum se face calculul valorii pentru expresiile simple, tara
subexpresii ?
b) Atunci cand fac calcule pentru expresiile compuse cum se
combina doua calcule succesive ale partilor acelei expresii ?
Este evident cain limbajele imperative, cum este C, in care exista
constructii sintactice sub forma de succesiuni de expresii ca <expr> ;
<expr> sau <expr>, <expr> intrebarea nu este chiar banala. Pare
banala deoarece toti avem niste asteptari de Ia un calculator, cum ar
fi sa evalueze expresiile in ordine sau parametrii unei functii in
ordine, dar lucrurile nu stau totdeauna neaparat asa cum ne-am
astepta iar lui, computerului, trebuie sa i se explice scriind corect
program interpretorului cum sa faca totul. Fiindca nu stie nimicl
c) Pe urma, ftlind cum se comblni doui calcule succeslve,

0 introducere in Haskell 98 prin example - 142 - Dan Papa


intrebarea urmitoare este care anume calcule ar trebui
combinate 'i in ce ordine atunci cAnd socotim valoarea unei
expresii compuse, a unei structuri sintactice compuse. Ginditi-va Ia
if...then ...else ... care nu-~i evalueaza decAt una dintre expresiile
finale. Este un caz aparte, in general Ia calculul valorii structurilor
compuse (fie ele simple expresii sau intregi instructiuni structurate cu
substructuri) semantica/semnificatia intregului se calculeaza ca o
functie de toate sub-componentele structurii. Care adesea trebuiesc
toate evaluate.
Mentionam faptul ca pe vremea interpretoarelor monolitice, un
interpretor se definea raspunzand numai intrebarilor a ~i c. Dar lipsa
unui nivel intermediar care sa abstractizeze notiunea de calcul ~i

cea de operatie cu calcule tacea sa devina inoperante toate


formulele de Ia punctul c, imediat ce schimbam (chiar numai
extinzand-o) multimea valorilor de Ia intrebarea a ~i modul lor de
calcul sau de stocare. Acest lucru afecta negativ eficienta activitatii
de service soft a limbajelor ~i dadea na~tere multor proiecte paralele
~i inchise.

Un nivel intermediar de felul celui de Ia punctul b, ar separa definitiile


sintactice - acelea care ne spun ca un term este compus din altii -
de cele semantice care spun ce valoare are, evidentiind structura
algebrica a operatiilor cu calcule. Aceasta din urma structura
define~te doar cum se succed calculele, cum se transforma o
valoare simpla intr-un calcul ce-o va produce, cum davin anumite
operatii cu valori operatii cu calcule. Vom vedea ca o structura

0 introducere Tn Haskell 98 prin exemple - 143 - Dan Popa


algebric~ ce modeleaz~ bine notiunea de univers de calcule este
monada.

0 introducere in Haskell 98 prin example - 144 - Dan Popa


Monada

Despre: 0 structura algebrica numita monada

Vom introduce notiunea de monad~ . notiune util izat~ intensiv Ia


programarea In Haskell. Facem observatia c~ monada, fiind o
notiune din teoria categoriilor $i fiind extrem de general~ . poate fi
introdus~ $i a fost prezentat~ deja In mai multe moduri:
- D. Espinosa In teza sa de doctorat o prezint~ ca o generalizare a
monoidului, (Esp-95] $i i ndic~ drept s urs~ o lucrare a lui
Maclane din 1971 .
- Mai recent, John, N. Shutt folose$te ca metod~ de a o defini
parcurgerea unei serii de notiuni de teoria categoriilor: categorie ,
functor, transformare natural~ . adjunctie, monad~ . [Shu-03] ).
- in lucr~rile lui P. Wadler [Wad-92abc] se introduce monada cu
ajutorul notiunilor de functor, premonad~ $i monada, recurgandu-
se Ia 7 axiome care surprind interactiunea a 3 operatii: map, unit $i
join.
- Cea mai simpl~ defin~ie (capabi l~ s~-i multumeascA pe

0 introducere in Haskell 98 prin exemple - 145 - Dan Popa


matematicieni) introduce monada ca pe o tripleta formata dintr-
un functor (notiune din teoria categoriilor) $i doua transformari
naturale (notiune din teoria categoriilor). Ea este cea mai
apropiata de implementarile In Haskell de aceea o vom folosi $i
noi $i ne vom referi Ia monada ca Ia o tripleta scrisa (M, >>= ,
return) sau intr-o notatie mai veche (M, bindM, unitM).

in Haskell functorul de care e vorba este un banal constructor de tip


care similar cu cei binecunoscuti de Ia arbori sau liste. De fapt din
punct de vedere matematic un constructor de tip din Haskell este un
functor care duce tipuri in alt tip, de exeplu tipurile "a" in "arborii cu
frunze de tipuri a"; (notati Tree a).
Cele doua transformari naturale se scriu In Haskell ca doua functii
polimorfice. Sunt numite unit (sau return) $i bind.

Despre operatorul bind notat uneori ' bind ' iar mai recent (>>=) :
in Webster's New Century Dictionary am gasit numele traditional al
acestui operator laolalta cu traducerea denumirii sale:

bind - to tie toghether with a rope - a lega cu o funie


- to hold or restrain -a retine, a restrictiona
- to fasten together the pages of a book - a coperta o carte

El are numele $i tipul ca mai jos:


bind:: M a-> (a-> M a)-> M b .

0 introducere In Haskell 98 prin exemple - 146 - Dan Popa


in limbajul Haskell, mai exact In fi~ierul cu descrierea clasei Monad
el este notat lnsa cu:
>>= :: M a-> (a-> M a) -> M b
Despre operatorul return: Remarcati ~i faptul ca structura algebrica
de monada folose~te Inca un operator, return, al carui rol este de a
aduce, (de a injecta), valori In monada:
return :: a -> M a
Este normal sa avem un asemenea operator atunci cand facem
succesiuni de calcule cu valori monadice. Calculele trebule sa
lnceapi de undeva, cu o prima valoare monadici. lar pe aceasta
o produce o functie de Ia a -> M a. Aceasta este operatorul return.

Definirea acestor operatori intr-un fel sau altul, respectand o


serle de proprietatl (axiome) ale structurii algebrlce de monada
di na,tere unor monade (concrete). Ele se folosesc [Wad-92b),
[Hut-98] Ia realizarea de compilatoare ~i/sau interpretoare
adaptabile (monada parserelor, monada cu stari ~I string de 10) sau
Ia operatii de 1/0 scrise In Haskell In do-notatie.

La constructia interpretoarelor adaptabile (cu timp redus de service


de soft) o monada (de obicei una dintre monadele cu stari) serve~te
Ia a separa elementele simple de modul lor de combinare. Astfel
acest mod de combinare poate fi dat in maniera abstract<'l,
independent de elementele care se combina. in acest mod
monadele contribuie Ia realizarea adaptabilit<'ltii programelor,
reducerea timpului de service de soft ~i cre~terea productivit<'ltii

0 introducere fn Haskell 98 prln exemple - 147 - Dan Popa


muncii de programare.

Remarc~: datorit~ gradului lnalt de generalitate pe care 11 are


notiunea de monad~. datorit~ felului general In care ea modeleaz~

combinarea de componente ~i productivit~tii ce rezult~. mai multi


autori au folosit monadele Ia realizarea unor p~rti ale
interpretoarelor sau compilatoarelor:
- Ia nivelul sintaxei serve~te Ia combinarea parserelor deoarece
combinarea parserelor se poate modela cu o monad~ - monada
parserelor (cititi de Graham Hutton lucrarea despre "monadic
parsing in Haskell" [Hut-98] }. Actualmente exist~ deja propuse de
cercet~tori biblioteci standard de parsere modulare care se
combin~ monadic, una din cele mai complexe fiind Parsec,
propus~ de Daan Leijen [Lei-01) care l~i propune s~ lnlocuiasc~

mai vechiul Parselib- acea clasica bibliotec~ de parsere.


- Ia nivelul instructiunilor ma~inii virtuale, deoarece combinarea
efectelor (tranzitilor automatului cu stiv~ care este ma~ina

virtual~} se poate modela tot printr-o monad~ (ori monada cu stari


ori monad~ cu stari ~i string de ie$ire ... etc } $i aici putem cita
lucrari ale lui P. Wadler cum este [Wad-92b].

6.1 Monada - scurta prezentare (de interes istoric)

De$i prezente de mult timp In peisajul matematic, unde se integrau


In domeniile topologiei algebrice $i teoriei categoriilor, (cadru In care

0 introducere in Haskell 98 prin example - 148 - Dan Popa


ni$te variante ale lor erau cunoscute sub numele de Triple Kleisli)
monadele, ni$te structuri algebrice deosebite (care totu$i , in ultima
instanta nu-s cu mult mai complicate decat grupurile, corpurile sau
inelele studiate Ia liceu) $i-au facut intrarea in lumea computer-
science-ului odata cu lucrarile, cursurile $i articolele profesorului
Eugenio Moggi. Monadele s-au dovedit atat de flexibile incat
Eugenio Moggi a putut descrie cu ajutorul lor practic toate aspectele
limbajelor de programare, intr-o maniera unitara.
Tot datorita lor, limbajele functionale nu mai sunt nevoite sa includa
structuri de control imperative. Toata programarea imperativa se
poate face acum in limbajele functionale doar cu ajutorul do-notatiei,
o scriere - de fapt un tel de macrouri - cu aspect imperativ bazate pe
monade. Ceea ce permite unui programator ce folose$te Haskell sa
programeze intr-un stil foarte flexibil, ba in mod imperativ ba in mod
functional, ba combinandu-le $i totul tara a reduce fiabilitatea
aplicatiei I ~i -i permite sa programeze cu aceea$i sintaxa a do-
notatiei inlantuiri de procese de diverse naturi:
- procese cu afectarea unei stari sau a unei structuri de stari,
- procese de analiza sintactica,
- procese cu rezultate calculate paralel,
- procese cu schimbari de stari $i text Ia ie$ire,
- operatii de 1/0 traditionale etc.

La un calculator electronic clasic un calcul este o trecere de Ia o


stare initiala (imaginati-va starea memoriei, cu toate valorile
variabilelor in locatii) Ia o alta stare, finala. Acest lucru face ca o

0 introducere in Haskell 98 prin exemple - 149 - Dan Popa


monada anume, monada stArilor (~i derivate ale ei) sa fie importanta
pentru modelarea calculelor de tip imperativ, a compilarilor ~i a
operatiilor de 10 .

6.2. 0 lucrare celebra ,1 notatllle de atuncl


in lucrarea lui Philip Wadler, The Essence of functional programming
(Univ. Glasgow, 1992 pg 2) monada prezentatA era o structurA
algebrica formata din 3 componente: M, unitM ~i bindM. Notatia este
actualmente inlocuita cu operatoriii citati in primul capitol (M a, >>=
,return) ~i cu mai noua do-notatle, un fel de macrodefinitie. Acolo
M este numele monadei. in Haskell, M va fi numele unui constructor
de tip polimorfic. Cand acestui constructor de tip cu parametru i se
va da un tip concret a ca parametru vom obtine un tip compus M a,
un subset de valori din monada, cu care operatorii unitM ~i bindM
pot opera. (Notati ca ~i unitM ~i bindM sunt functii polimorfice, au o
schem~ de tip cu variabile pot opera cu elemente din mai multe tipuri
compuse) .
unitM este functia poli morfic~ care transfer~ o valoare din tipul a i n tipul
M a . Schema sa de tip este: unitM ::a-> M a. Cu ease transfer~ valori
din tipul a In monad~ . Ca idee, transform~ valorile de tip a In calcule care
vor produce acele valori - ca ~i cum ai pune o jucMe cadou intr-un
ambalaj de Craciun ~i ai oferi-o Tm pachetat~ astfel). Primitorul va trebui s~

desch id~ pachetul ~~ s~ porneas~ ju~ria ca s~ obtin~ rezultatul. Da~

pe postul juc~riei cu baterii in cutie este o functie - ~i de multe ori este -


atunci ei I se va da (nu o baterie ci) argumentul necesar pentru a ob~ne

rezultatul.

0 introducere in Haskell 98 prin example - 150 - DanPopa


Observati ca: o monad~ se preteaz~ model~rii proceselor de calcul cu
oric~te tipuri II De exemplu Ia implementarea interpretoarelor limbajelor
de progarmare, semnificatia unei expresii intregi va fi un calcul producator
de intregi care are tipul M lnt sau M Integer iar semnificatia unei
instructiuni - care convenim c~ produce un nimic notat () - va fi un M ().
La fel, in monada de 10, un read care produce un tnt va fi de tipul 10 lnt
iar un putStr sau un print care doar scrie pe ecran tara s~ obtin~ el vreo
valoare nou~ (convenim iar c~ el produce o valoare () ) are tipul 10 ().
Actualmente numele unitM este inlocuit cu return at~t in definitia clasei
monadelor c~t ~~in do-notatie. Dar este ecela$i concept ca vechiul unltM.

bindM este o functie cu semnatura blndM :: M a-> (a-> Mb) -> Mb


care exprima felul cum se combina un calcul ce da un rezultat de tip
a cu un calcul capabil sa transforme acel rezultat de tip a intr-o
valoare de tip M b din monada. (Pentru matematicieni spunem c~ aici
poate fi plasat~ orice functie care verifica anumite legi algebrice -
axiomele monadei.) Serve~e Ia definirea modului de inlantuire dintre un
calcul care da un rezultat ~~ un calcul (o functie) care pri me~te Ia intrare o
valoare (nemonadica).

P. Wadler exprim~ privitor Ia acest sublect, (in lucrarile sale), c~teva ideii
fundamentale:
1) 0 functie f de Ia tipul a Ia b, f ::a-> b
devine in universul monadic o functie f :: a-> M b ( de Ia a IaMb) .
Acum ea prime$te un argument ca ~~ inainte dar returneaza o valoare
monadica.
2) Un interpreter in loc sa fie o functie cu semnatura

0 introducere in Haskell 98 prin example - 151 - DanPopa


lnterp ::Term-> Environment-> Value este mai curAnd ceva de tipul
lnterp ::Term-> Environment-> M Value.

Distinctia de mai sus este foarte important~ pentru limbajele functionale


interpretate. intr-un asemenea limbaj un term functional este o abstractie
(functle anonimi) scrlsi ca lambda expresie, o functie descri~.

denotat~ (nu o valoare obi~nuit~). lar ei i se asociaz~ ca valoare


semantic~ un proces de calcul nu o valoare final~. Haskell-i~tii avansati
v-ar spune ~ in sistemul de supratipuri at haskell-ului, valoare are soiul
(eng. Kind) "*" iar functia are soiul"*->*", deci chiar au naturi diferite.
AnticipAnd putin ~~ urm~rind ideea mai departe pAn~ in domeniul
semanticii vom vedea c~ aplicarea unei functii (din limbajul de
implementat) asupra unul term se va face cu un apply care va avea
schema de tip (idee inspirat~ din lambda calcul):

apply :: Value ->Value-> M Value.

far functia de c~utare a valoril unei variblle in memorie (environment) va


avea semn~tura :

lookup:: Name-> Environment-> M Value

Unde Name e tipul care cuprinde numele variabilelor (sinonim cu String},


Environment- contextul de evaluare- este lista perechilor variabile-valori
lar M Value sunt valorile Value aduse i n monad~ . devenite procese de
calcul care pproduc acele valori din tipul Value.

0 introducere in Haskell 98 prin example - 152 - Dan Popa


6.3 Functla pollmorfica "unitM" sau "return"

Rolul lui unitM este sa transfere valorile simple sau compuse direct
In monada adica in capsulele monadice. Altfel spusa sa transforme
va/ori in procese de calcul care ar produce acele valori. Valorile pot
apartine unor diverse tipuri. Daca vreti . unitM este versiunea
monadica a functiei identice. Ea a fost, a$a cum am mai spus,
ulterior denumita return, odata cu inventarea do-notatiei.
Vom vedea ca exista chiar o monada, monada identitate, in care
tipul a coincide cu tipul M a pentru orice a (deci constructorul de tip
M este $i el o functie identitate) iar in acest caz unltM este chlar
functia identica.

lata un exemplu: Cum intervine unitM In stabilirea semanticii unor


constante dintr-un limbaj interpretat.
Descriem modul de interpretare a constantelor, structuri
arborescente construite cu constructorul de tip utilizator "Con", in
contextullistei de variabile e (din eng. environment).
interp (Con i) e = unitM (Num i)
adica functia interpreter, avand primul argument constanta i $i al
doilea argument orice Environment (lista cu valori ale variabilelor)
ignora aceasta lista (e) $i returneaza valoarea Num i, adica numarul i
in "varianta monadica". Putem intelege de aici ca rezulta procesul de
calcul care-1 va produce pe acel Num i, (Num i este un tip dintre cele
ale valorilor posibile de obtinut prin interpretare iar unitM il face
valoare monadica. lmaginati-va ca 1-ar lnchide pe Num i intr-o

0 introducere Tn Haskell 98 prin exemple - 153 - Dan Popa


capsula transformandu-lln procesul producator de Num i: M (Num i).
Remarca: Toate versiunile noi de Haskell (spre deosebire de vechiul
interpreter Gofer) folosesc pentru unit denumirea de return.

6.4. Funqia polimorflca "blndM"

Daca unitM era in universul monadei analogul functiei identice,


bindM este analogul compunerii de functii.
Doua functii obi$nuite f :: a-> b $i g :: b -> c (matematicienii ar fi scris
f : a-> b $i g : b -> c ) se compun in maniera uzuala formand functia
anonima denotata de lambda expresia:

(\ a -> let b =f a in g b)

iar aceasta are tipul a -> c.


Similar, doua functii din universul monadei, prima f ::a-> M a $i a
doua, g ::a-> M b se compun scriind:

(\a-> fa 'bindM' (\b ->g b))

sau cu notatia altemativa, moderna, pentru operatorul bind:

(\a-> fa >>= (\ b ->g b))


Tipuri: Ma a -> M b
Observati ca: f a are tipul M a , functia (\ b ->g b) care-1 duce pe b in
g bare tipul a-> M b iar operatorul bind, (>>=), are tipul Ma -> (a->

0 introducere Tn Haskell 98 prin example - 154 - DanPopa


M b)-> M b astfel ~ prin aplicarea lui, tot ce urmeaza dupa "(\a->"
adica "f a >>= (\ b -> g b)" are tipul M b ceea ce face ca intrega
paranteza sa aiba tipul a -> M b.

Proprietatile algebrice ale operatiilor unei monadei {monada e o


structura algebrica ale ~rei operatii verifica anumite axiome - ca
orice structira algebrica) permit sa se demonstreze ca aceasta
compunere este asociativa ~i ca unitM (mai nou numit return) este
element neutru ~i Ia dreapta ~i Ia st~nga pentru ea.
Atentie: Proprietatile algebrice ale operatiilor trebuie verificate inainte
de a folosi aceste operatii in programe sau de a scrie do-notatie.
Functionarea corecta a programelor, inclusiv a celor care folosesc
do-notatla va depinde esential de respectarea legilor monadei. Veti
fi obligati sa faceti niscaiva calcule matematice pentru a va convinge
ca structura algebrica pe care o propuneti este intr-adevar o
monada. Dar asta numai daca inventati o monada noua.

Operatorul bindM era folosit {Ia data aparitiei acelor lucrari) pentru a
descrie in maniera compozitionata semantica structurilor sintactice
cu substructuri, atunci c~nd nu se folose~te do-notatia. Luam
{asemenea profesorului lui Wadler) ca exemplu un term care este o
suma de doi subtermi iar interpretorul it va prelucra interpret~nd

primul subterm u, interpret~nd at doilea term ~i pas~nd rezultatele


unei functii care face adunarea. Aceasta ar putea fi ~i i n universul
monadei procesul care produce valaoarea a+b adica de procesul
add a b = return (a+b).

0 introducere in Haskell 98 prin example - 155 - Dan Popa


inlantuirea operatiilor este asigurata de operatorul bindM,
interp (Add u v ) = i nterp u e 'bindM'
(\a -> i nterp v e · b i ndM ' ( \b ->
add a b))

Nota: Este mai vizibil pentru cititor efectul daca scriem in acest mod:
interp ( Add u v ) = interp u e · binc!M' (\ a ->
interp v e · bind.M' ( \b ->
add a b ))

Aici unii cititori ar putea intreba de ce nu-s puse altfel parantezele


dar va puteti da seama ca aici intervine asociativitatea operatorului
bindM- vedeti axiomele monadei (on-line).
in programele ce vor fi scrise de dumneavoastra veti folosi probabil
mai curand ">>=" $i "return" decat "bind" $i "unit". Sau, $i mai
probabil, dupa ce veti defini procesele de calcul (capsulele) din
monada preferata, dupa ce veti defini crearea proceselor care
returneaza o valoare prescrisa cu return $i inlantuirea lor cu bind
(>>=) , daca veti fi siguri ca s-a format o monada (ceea ce
presupune verificarea matematica a indeplinirii axiomelor - atunci
cand e vorba de monade nou inventate), ei bine, in acest caz puteti
programa in do-notatie.

6.5.Transcrlerea semanticii in do-notatie

Despre: Semantica, do-notatia din Haskell, transcrierea regulilor

semantice in do-notatie, conditii necesare ca do-notatia sa fie

0 introducere in Haskell 98 prin example - 156 - Dan Popa


conforrna cu programarea imperativa, legile monadei, definitia
monadei.

Descrierea semanticii unei simple adunari de termi intr-un limbaj de


expresii se poate face in mai multe moduri, depinzand de semantica
utilizata. in acest capitol vom prezenta un instrument puternic pentru
transcrierea semanticilor in limbajul functional Haskell: do-notatia.
Sa luam un simplu exemplu de regula semantica din lucrarea [Cio-
96] (Cap. 3.2.3. pg 42) scris intr-o notatie u~or modificata:

E 1- t1 => a E 1- t2 => b E - contextul


t1 ,t2 - termi
E 1- t1 + t2 => a add b t1 +t2- terrnul compus

Paragraful din care am extras-o este din capitolul despre semantica


dinamica bazata pe "environment" (trad. - mediul de calcul). in
aceea$i carte, in capitolul dedicat semanticilor denotationale a
expresiilor aritmetice (Cap. 4.11 , pg.98) este data urrnatoarea
formula (notatiile pot fi iara$i U$Or diferite):
A[[e1 + e2]] (M) =A[[e1]](M) add A[[e1]](M)
Limbajul Haskell permite transcrierea, implementarea acestor
semantici chiar $i In cazul prezentei efectelor laterale. prin ceea ce
se nume$te do-notatle.
Informal, do-notatia este o scriere cu aspect imperativ practicata In
limbajul functional pur care este Haskell. (Unul dintre locurile unde
este folosita este Ia scrierea programelor sau portiunilor de

0 introducere tn Haskell 98 prin exemple - 157 - DanPopa


programe care utilizeaz~ instructiuni de 1/0.)
in realitate do-notatia este un "syntactic-sugar" pentru notatii
realizate cu operatorii >>= ~i return, ~titi deja, cei care au tipurile:
return :: a -> M a
>>= :: M a-> (a-> Mb) -> Mb
Formal, do-notatia, care e un fel de macrosubstitutie, se define~te

recursiv, (pentru orice variabile x, actiuni m ~i expresii I actiuni e) ca


fiind:
do { e} =e
do { m ; e} = m>>= (\ _ -> do {e} }
do { x <- m; e} = m >>= (\x ->do {e})
iar in unele lucr~ri cum ar fi : (Lab-**] apare ~i o a patra regul~ :

do { let exp; e} = let exp in do {e}


Aplicatie: Reguli semantice cum este cea de mai sus se scriu in
Haskell, in do-notatie (ascunzAnd detaliile despre "environmenr in
felul cum sunt definiti operatorii >>= ~i return) :
do { a<- t1 ;
b <- t2;
add a b}
Aceast~ descriere este "syntactic-sugar" pentru expresia
echivalent~:

t1 >>=(\a-> t2 >>= (\ b ->add a b)}


Suportul acestor calcule este o structur~ algebric~ (despre care vom
vedea c~ este chiar monada} alcatuita dintr-un constructor M de
tipuri (cu parametru) ~i dou~ functii polimorfice cu tipurile de forma
(cunoscut~. dar o repet~m ca s-o retineti):

0 introducere in Haskell 98 prin example - 158 - Dan Popa


return :: a -> M a
>>= :: M a-> (a-> Mb) -> Mb
care satisfac o serie de e9alitati tara de care do-notatia nu s-ar
conforma intuitiei pro9ramatorului care utilizeaza limbaje imperative.
Aceste axiome I ecuatii de indeplinit (care ar face ca do-notatia sa
functioneze similar cu programarea imperativa) nu sunt satisfacute
automat de orice operatori >>= ~i return, dar Ia monadele traditional
folosite, sunt. lata aceste axiome. Nu uitati ca prezenta lor
9aranteaza ca puteti citi do-notatia ca ~i cum ar fi pro9ramare
imperativa, de~i in realitate e pro9ramare functionala pura.
do { x<- return t ;
fx } = ft

do { x <- m;
return x} = m

do { x <- m; do { y <- do {x <- m;


do { y <- fx ; = fx} ;
9 y}} 9 y}
Axlomele monadel scrlse sub forma de egalltatl de do-nota111.

Aplicand definitia do-notatiei ~i transcriindu-le in expresii formate cu


>>= ~i return se obtin imediat formele lor echivalente:
return t >>= (\ x -> f x) =ft unde observ: (\ x -> f x) = f
m >>= (\x ->return x) =m unde (\x ->return x)=retum
~i ultima:
m >>= (\x -> (f x) >>= (\y -> 9 y) ) =
( m >>= (\x -> (f x))) >>= (\y -> 9 y)

0 introducere in Haskell 98 prin example - 159 · Dan Popa


Teorem~: Aceste conditii necesare pentru ca do-notatia s~ fie
similar~ program~rii imperative sunt indeplinite dac~ $i numai da~

structura format~ de (M, >>=, return) este o monad~ - a$a cum a


prezentat-o $i utilizat-o P.Wadler.
Demonstratia este imediat~: comparfmd relatiile de mai sus cu legile
monadei din lucrarea lui P.Wadler se constat~ c~ cele 3 propriet~ti

intuitive necesare ale do-notatiei sunt exact legile monadei


exprimate in do-notatie (cu observatia ~ Ia prima lege apare o
variabil~ suplimentar~ care se poate inl~tura din ambii
membri).Q.E.D.

6.6. Operator! de "lifting"

Surse variate inclusiv [Esp-95] indic~ drept revolutionar~ ideea


pornit~ de Ia lucr~rile lui E.Moggi [Mog-89b],[Mog-90] de a considera
interpretorul nu ca funefie de Ia termi $i environment Ia valori ci ca o
functie de Ia termi $i environment Ia valori monadice.

Acest lucru impune, in realizarea interpretoarelor care se bazeaz~

pe aceast~ idee, transformarea calculelor din universul valorilor in


ni$te calcule similare din universul monadei. Lucr~rile de specialitate
numesc acest fenomen "lifting" sau "lifting monadic". in [Esp-95],
acest lucru este realizat folosind o serie de functii de nivel superior
scrise in Scheme, care transform~ operatiile ce returneaz~ valori in
operatii care returneaz~ valori monadice. Lucrarea [Esp-95], prezint~

ace$ti operatori de lifting (eng - "lifting operators") in Cap 1.4.1 fig

0 introducere Fn Haskell 98 prin exemple - 160 - Dan Popa


I' , 1:, 1

1.1 . Textul de mai jos, In Scheme - poate fi citit de cunosc~torii de


LISP:

(define ((lift-p1-a0) unit bind op) p1)


(unit (op p1 )))

(define ((lift-p0-a1 unit bind op) d1)


(bind d1
(lambda (v1)
(unit (op v1 )))))

(define ((lift-p0-a2 unit bind op) d1 d2)


(bind d1
(lambda (v1)
(bind d2
(lambda (v2)
(unit (op v1 v2)))))))

(define ((lift-p1-a1 unit bind op) p1 d1)


(bind d1
(lambda (v1)
(unit (op p1 v1 )))))

(define ((lift-if unit bind op) d1 d2 d3)


(bind d1
(lambda (v1 )

0 introducare In Haskal/98 prin example - 161 - Dan Popa


{op v1 d2 d3))))

Notati ca ace~ti operatori de lifting actioneaza in cadrul creat de o


monada {mai exact de operatorii acesteia "unir ~i "bind") facand
lifting monadic operatorului oarecare "op". Variabilele di, i=1 .. 3 sunt
variabilele libere din expresia urmatoare.

Transcriind in Haskell expresiile functiilor de mai sus se obtin


urmatoarele expresii cu "bind" ~i "return" utilizabile ~i utilizate Ia
realizarea interpretoarelor:

return {op p1)


d1 >>= (\ v1 -> return (op v1))
d1 >>= (\ v1 -> d2 >>= (\ v2 ->return {op v1 v2)))
d1 >>= (\ v1 -> return (op p1 v1))
d1 >>= (\ v1 -> op v1 d2 d3)

... care Ia randul lor pot fi transcrise imediat (folosind definitia do-
notatiei din orice manual de Haskell) in do-notatie:
do {return (op p1)}
do { v1 <- d1 ; return (op v1)}
do { v1 <- d1 ; v2 <- d2; return (op v1 v2)}
do { v1 <- d1 ; return (op p1 v1)}
do { v1 <- d1 ; op v1 d2 d3 }
1

II Vom vedea ca Ia scrierea interpretoarelor (ba chiar ~i a


II

0 introducere in Haskell 98 prin example - 162 - Dan Popa


compilatoarelor) regulile semantice de calcul a rezultatului sau de
generare a codului (altfel de rezultat) au chiar aceste forme - sau
variante extinse structurat ale lor - de exemplu cu o do-notatie in
alta do-notatie- ca Ia programarea structurata.

6.7. Observatll ,1 concluzii


Observatle: in Haskell nu este necesar sa definim ace$ti operatori
de lifting ei put~nd fi inlocuiti cu o transcriere directa a semanticii in
do-notatie. Acest lucru recomanda odata in plus limbajul Haskell
(deosebit de alte limbaje functionale, el fiind inzestrat cu aceasta do-
notatie) Ia scrierea de interpretoare ($i compilatoare) adaptabile sau
extensibile. Un exemplu de acest tel - partea generatoare de cod a
unui compilator (unul modular I!) - este prezentat in capitolul al 9-lea.

Concluzia intai: Monada se poate defini $i ca o structura algebrica


(M, >>=, return) pentru care do-notatia din Haskell functioneaza
exact daca structura se conformeaza celor trei reguli - axiomele
monadei.

Concluzia a 11-a: Daca dorim sa exprimam semanticile in do-notatie


structura algebrica de monada este un instrument util $i necesar
(lnterpretoarele fara monade au adaptabilitatea limitata.)

Concluzia a 111-a: Do-notatia existenta in Haskell recomanda acest


limbaj ca platforma pentru realizarea de interpretoare adaptabile.

0 introducere in Haskell 98 prin exemple - 163 - Dan Popa


6.8 Un lnterpretor comentat, reallzat cu o lnstan13 a clasei
"Monad"

Probabil v-ati intrebat, citind capitolul precedent, in care foloseam


"bind" ~i "unit" ~i precedentele paragrafe din capitolul acesta, in care
am prezentat clasa "Monad", de ce nu este scris interpretorul direct
cu ajutorul clasei "Monad". R~spunsul este simplu: din motive
"istorice" am p~strat exemplul ~i notatiile ca in lucr~rile originale ale
prof. Philip Wadler. Varinata in do-notatie a acestui interpretor
figureaz~ ins~ in anexa eartH.

A venit momentul s~ vedem cum se scrie (a~a cum ne invat~ prof.


P.Wadler), interpretorul cu ajutorul unei declaratii care creaz~ o
instant~ a clasei "Monad", folosind totodat~ ~i noile notatii : >>= ,
return precum ~i mai noii >> ~i fail.

s~ incepem cu definirea datelor:


> data M a = Succes a I Error String
Tipul de date cu constructor de tip M va fi de fapt tipul elementelor
monadei. Acest tip de date va fi inzestrat in continuare cu ni~te

operatii concrete (cu ocazia instantierii II) deoarece declaratiile din


clasa "Monad" sunt doar ni~te semn~turi de functii, oarecum
asem~n~toare functiilor virtuale din C++. Deoarece un asemenea
element din monada M va avea semnificatia de "proces de calcul" iar
un proces de calcul se poate desf~~ura :

0 introducere tn Haskell 98 prin exemple - 164 - Dan Popa


- cu succes, d~nd ca rezultat o valoare dintr-un tip oarecare a sau
- cu rezultatul "Eroare", urmat de textul explicativ al erorii,
din aceste cauze tipul declarat de noi {avAnd constructorul de tip M)
va fi o reuniune de doucl multimi de date, av~nd constructorii de date
"Succes" $i "Error". L-am declarat:
data M a = Succes a I Error String
Observati eel a-ul, acel tip variabila - necunoscut - din dreapta
semnului egal apare $i In stanga fiind folosit de constructorul de date
Succes pentru a construi date care corespund diverselor feluri de
calcule lncheiate cu succes. Corespunzator diverselor tipuri care vor
lua locul lui a, vom avea In monadcl diverse feluri de calcule care
dau cu succes rezultate din tipurile corespunzatoare.

Oat fiind cclln Biblioteca Standard {/usr/sharelhugs/lib/Prelude.hs -


pe sistemele Linux Mandrake 8.2 ) este definita clasa "Monad" nu
trebuie sa mai scrieti declaratiile de mai jos. Le prezint doar ca ni$te
comentarii, pentru a mentine claritatea expunerii. Reamintiti-va de
existenta lor.
> c l ass Monad m where
> (»=) m a -> ( a -> m b) -> m b
> (») m a -> m b -> m b
> return a -> m a
> -- fail String -> m a
>
> m >> k = m >>= \_ -> k

Datorita acestor declaratii din clamsa "Monad" tot ce aveti de tacut

0 introducere in Haskell 98 prin example - 165 - Dan Popa


atunci cand obtineti o monad~ printr-o instantiere a acestei clase
este s~ precizati exact comportamentul operatorilor din lista de
sus.
Specific~m exact monada care va servi ca "baz~" interpretorului. Ea
se va numi in cazul nostru M, (acesta fiind doar constructor de tip nu
$i constructor de date).
> i nstance Monad M where
> (Succes a) >>= k k a
> (Error s) >>= k Error s
> return a Succes a
> fail s = Error s

Coment~m pe rand aceste functii, instante ale declaratiilor lor de tip.


Prima afirm~ c~ un calcul cu incheiat cu succes care d~ ca rezultat o
valoare din tipul a, urmat de un alt calcul k ce poate primi acel a are
ca efect calculul k aplicat valorii de tip a anterior obtinute.
A doua afirm~ c~ un calcul eronat, incheiat prin e$eC, calcul care d~

mesajul de eroare s, urmat Ia executie de calculul k r~mane un


calcul eronat care d~ tot mesajul de eroare s, iar k nu este luat in
consideratie. Observati c~ acest lucru corespunde comport~rii

intuitive a unui proces de calcul abandonat Ia aparitia unei erori:


Pa$ii urm~tori nu mai conteaz~ $i tot ce se d~ ca rezultat este
mesajul de eroare.
Return este $i aici noul nume pentru operatorul identitate din
monad~ . Efectul s~u este de a injecta valori i n universul monadei, de
a crea calcule care produc exact acele valori prescrise. lntuitiv

0 introducere in Haskell 98 prin exemple - 166 - Dan Popa


putem spune ca o constanta din tipul a se transforma, adusa In
monada, 7ntr-un calcul cu succes care obtine chiar acea valoare -
constanta . Evident nu este nici un motiv ca un asemenea calcul sa
fie reprezentat In monada printr-un calcul producator de e~ec.

deoarece constanta este binedefinita.


0 functie similara are fail, de a returna elemente construite cu
constructorul de date Error. Daca doriti, vi-I puteti imagina pe fail ca
un tel de supliment al lui return, deoarece stringurile mesajelor de
eroare nu sunt injectate direct In universul monadei cu return.
Aceasta functie fail este atAt de des folosita atunci cAnd se lucreaza
modular, cu monade, incat autorii setului de functii predefinite din
Biblioteca Standard au inclus-o in clasa "Monad".

Practic, pana acum am definit urmatoarele:

- succesiunea a doua calcule dintre care primul e lncheiat cu succes.


Aceasta include cazurile cAnd ambele calcule nu dau erori ~i cazul
cand un calcul cu succes e continuat de unul care da o eroare.

- succesiunea dintre un calcul eronat ~i unul cu succes, rezultatul in


acest caz este ca nu mai conteaza calculul urmator celui care
generase eroarea.

- calculele care dau valori constante, ele sunt calcule terminate cu


succes.

0 introducere in Haskell 98 prin example - 167 - Dan Popa


.---- J

- calculele care dau eroare $i felul cum se asociaz~ mesajul de


eroare unui asemenea calcul. De aceasta se ocup~ fail, prime$te
mesajul de eroare sub form~ de string $i-l transfer~ in universul
monadei ca un M (Error "acel string").
lnterpretorul este un evaluator al unui program, Ia fel ca evaluatorul
de expresii aritmetice pe care unii profesori il prezint~ Ia lectia
despre recursivitate pentru a da un exemplu de functii mutual
recursive. El prime$te programul, contextul de executie (adic~ lista
perechilor formate intra variabile$i valorile lor), $i returneaz~

rezultatul final. Programul initial parcurge o faz~ de analiz~

sintactic~ . de acolo rezult~ arborele s~u sintactic. Dup~ un proces de


promovare, ridicare, a operatorilor in nodurile p~rinte ($i alte cAteva
operatii printre care eliminarea unor detalii de sintax~ concret~ - cum
este "else" Ia if-uri) se obtine arborele operatorial. Un asemenea
arbore este de fapt acela care trebuie evaluat.
Exemplu: Un program scris intr-un limbaj functional, trecAnd prin
faza analizei sintactice $i semantice devine, in final, un arbore
operatorial, de exemplu ceva de forma:

(App (Lam "x" (Add (Var "x") (Var "x"))) (Add (Con 12)
(Con 69)))
Exemplul de mai sus este inspirat de eel de Ia pg. 3 a lucr~rii "The
essence of functional programming" de P.Wadler. El corespunde
unui program care in limbaj surs~ ar avea sintaxa:
( lambda X • X + X ) (12 + 69)
sau expresiei din Haskell

0 introducere rn Haskell 98 prin exemple - 168 - Dan Popa


(\ X -> X + X ) (12 + 69)

not~ndu-se astfel aplicarea abstractiei (functiei anonime) din


paranteza st~nga asupra sumei din dreapta. Structurile de date
necesare reprezentarii unor astfel de programe, in fond ni$te termi,
se declara ca mai jos:
> type Name String
> data Term Var Name
> Con Int
> Add Term Term
> Lam Term Term
> App Term Term
Le comentam rand pe rand : Deoarece String era un tip predefinit din
limbaj iar noi doream sa numim multimea identificatorilor "Name" a
fost nevoie doar de o declaratie type care introduce un sinonim de
tip, "Name" devenind sinonim cu "String". Tipul Term este insa un tip
nou creat prin reuniunea a 5 submultimi corespunzatoare celor 5
variante de termi. Constructorul sau de tip (care este in Haskell chiar
numele tipului) este Term. Constructorii de date vor proveni de Ia
cele 5 submultimi care se reunesc. Ei sunt:

Var- constructorul variabilelor. Observati ca numele variabilelor sunt


de tipul Name, lucru normal deoarece un identificator de variabila
este un caz particular de String.

Con - constructorul constantelor. 0 constanta este data sub forma


unui intreg, un element din tipul Int.

0 introducere In Haskell 98 prin example - 169 - DanPopa


Add - Constructorul sumelor de termi este practic simbolul
(operatorul) din radacina arborelui care reprezinta adunarea a doi
termi.

App - Constructorul pentru operatorul "aplicare", acel operator


invizibil dintre cele doua paranteze ale exemplului de mai sus.
Operanzii sai sunt o functie ~i un argument.
Observatie: Limbajele care, in urma traducerii din sintaxa abstracta
in arbore operatorial produc arbori operatoriali mai complec~i pot
avea mai mult de 5 constructori de date de acest fel.
intrucat vom interpreta cu acest (back-end de) interpreter un mic
limbaj functional in care termii pot fi ~i abstractii notate ca lambda
expresii, multimea valorilor care rezulta in urma interpretarii va fi Ia
randul ei impartita in submultimi corespunzatoare:
intregilor (se zice ca soiul *) ,
functiilor (care au soiul *->*) ~i
erorii.
incepatorii sunt sfatuiti sa includa intotdeuna in multimea valorilor ~i
o valoare "Wrong". Este utila pentru a trata diverse cazuri in care nu
se poate obtine o alta valoare.

> data Value Wrong


> Nurn Int
> Fun (Value -> M Va lue )

0 introducere fn Haskell 98 prin exemple - 170 - Dan Popa


Aici putem face observatia ca o lambda expresie, cum este cea din
exemplul dat
( 1 ambda X • X + X ) ( 12 + 6 9 )
atunci cAnd va fi evaluata de interpreter va trebui sa produca ceva
care asociaza
- o valoare (aici valoarea lui x)
- cu calculul (aici "aduna x cu x ") care se va face folosind acea
valoare in monada M. Observati deci ca valoarea unei lambda
expresii nu va fi un Num(ar) ci o Fun(ctie). 9i aceasta lncapsulata.

Afi$area valorilor acestor rezultate:

Rezultatele diverselor calcule, de fapt valorile cu care se termina


diversele calcule in monada M vor trebui afi$ate pentru uzul
operatorilor umani. Decidem ca un calcul terminat cu succes sa
produca mesajul :
"Succes: " urmat de valoarea calculata

iar un calcul terminat cu o eroare sa fie afi$<it ca:


"Eroare: " urmat de mesajul de eroare

Prin urmare vom declara, avlind grija sa tratam toate posibilitatile:


> showM (Succes a) " Succes :" ++ showval a
> showM (Error s) = " Eroare :" ++ s

Remarcati $i faptul ca n-am mai scris (tipul), semnatura functiei

0 introducere Tn Haskell 98 prin exemple - 171 - Dan Popa


showM. limbajul Haskell poate sa utHizeze insa tara probleme
functia deoarece poseda acel mecanism de inferente de tipuri . Va
deduce singur semnatura functiei, din modul de utilizare $i din felul
cum este scrisa.

lntentionam in vederea afi$arii sa convertim valorile intregi rezultate


din calcule in String-uri (ecranul este un spatiu in care se afi$eaza
String-uri) cu o functie numita showint. Transformarea din lnt In
String ceruta Ia afi$are o va face functia predefinta show. Este
suficient sa declaram pentru fiecare fel de numere cu care va opera
interpretorul (aici doar lnt) cAte o astfel de functie. Similar procedam
$i pentru celelalte submultimi ale tipului Value, valori care pot fi
rezultate ale unor calcule: le convertim In String-uri.
> s h owi nt : : Int - >String
> s h owi n t i = show i
Cele trei feluri de valori ale tipului Value vor trebui $i ele sa poata fi
afi$ate. Practic avem nevoie de o functie
showval :: Value - > Str i ng
ale carei $abloane (pentru pattern-matching) sa includa atAtea
variante cate am inclus in tipul Value.

> showval : : Val ue - > String


> showva l Wro ng " <wro ng> "
> showv al (Num i ) s h o wint i
> showval (Fun f ) "<functi on> "

0 introducere in Haskell 98 prin exemple - 172 - Dan Popa


Observati cA dacA am fi avut intre valori $i alte feluri de "Num"-ere
(din clasa Num) ar fi trebuit sA furnizAm cate o functie showX pentru
fiecare alt tip X care este intr-o situatie similara cu cea a lui lnt de
mai sus.
lnterpretorul lucreaza cu variabile cArora le asigneaza valori. El
poate evalua ceea ce i se da, In diverse contexte i n care anumite
valori, ale unor variabile, sunt deja date. Astfel poate rAspunde Ia
intrebari de felul : "Cat face x + 1 cand x are dinainte valoarea 5 ?"
Pentru aceasta are nevoie de un context (eng. Environment), o lista
de asociere lntre nume $i valori de variabile. 0 vom declara folosind
o sinonimie de tip:

> type Environment-> [(Name , Value)]

Deoarece tipul lista de perechi (perechi cu elemente din doua tipuri


diferite) se poate construi U$Or cu constructoriui { } $i ( , ) nu a
trebuit decAt sa-i dam un nume. ~i l-am numit chiar Environment.

Urmeaza interpretorul propriuzis.


> interp :: Term -> Environment -> M Value
> interp (Var x)e = lookupE x e
> interp (Con i )e = return (Num i )

Ne oprim aici pentru a comenta cele deja scrise. lnterpretorul preia


un program dat in sintaxa abstracta (adicA un Term), contextul
Environment in care este acesta evaluat (lista de perechi} $i obtine o
M Value, un element din monada, deci un calcul (despre care $1im

0 introducere in Haskell 98 prin exemple - 173 - Dan Popa


cA poate fi terminat cu succes sau cu eroare). Comentam felul cum
este el definit:

- lnterpretarea variabilei presupune cautarea ei intr-un context $i


producerea unei M Value continand rezultatul.
- lnterpretarea constantei returneaza un rezultat numeric, dintr-o
submultime a tipului Value, cea care are constructorul de tip Num.
Aici "returneaza" este folosit cu sensul ca interpretarea constantei
aduce valoarea in monada, cu functia return, transformand-o in
procesul de calcul care ne da acea valoare. Ea va participa $i Ia alte
calcule.
- lnterpretarea unei sume este urmatoarea:

> i n t erp (Add u v ) = i nterp u e >>=


> (\a - > i nterp v e >>= (\b ->
> add a b ))

Nota: Este mai vizibil pentru cititor efectul daca scriem in acest mod:

i n terp (Add u v) = i nterp u e >>= (\a - >


i nter p v e >> = (\b - >
add a b))

Cu alte cuvinte efectul este urmatorul: Se interpreteaza intai u in


contextul e $i rezulta o valoare. Aceasta va fi transmisa ("bind"} unei
functii anonime capabila sa faca cu acea valoare - pe care o
nume$te a - urmatoarele: intai interpreteaza v in contextul e. in

0 introducere in Haskell 98 prin example - 174 - Dan Popa


continuare aceasta valoare b va fi adunata cu a-ul dinainte. Operatia
add este adunarea obi$nuita dar nu a intregilor din lnt ci a valorilor
Num i din monada. in final semantica adunarii va fi acel calcul care a
evalueaza cei doi termi $i aduna rezultatele.

lnterpretarea unei lambda expresii este urmatoarea:

> interp (Lam x v) e return


> (Fun
> (\ a -> i n terp v ( ( x, a ) : e )) )

Rezultatul este o valoare, din submultimea tipului Value, creata cu


constructorul de tip Fun. in urma interpretarii rezulta functia care va
duce o valoare oarecare a in rezultatul evaluarii lui v in contextul
nou: e in care se adauga perechea ce spune ca x ia valoarea a.
lnterpretarea aplicarii:

> interp (App t u) e i n t e r p t e >>=


> (\ f -> interp u e >>=
> ( \a-> a p pl y f a ) )

ceea ce uneori se mai scrie a$a:


interp (App t u ) e = int e rp t e >>= ( \f ->
i nte r p u e >>= ( \ a - >
a pp l y £ a ) )

lnterpretarea aplicarii presupunea pe de o parte sa se interpreteze t

0 introducere in Haskell 98 prin exemple - 175 - Dan Popa


in contextul e iar rezultatul este o data construita cu constructorul de
date Fun. Aceasta va fi transmisa ca parametru functiei urmatoare.
Sa vedem ce calculeaza ea. Functia interpreteaza pe u in contextul
e iar valoarea capatata a va folosi pentru a purcede Ia aplicarea lui f
(argumentul primit, data construita cu Fun) asupra sa.
Dupa cum ati observat, Ia scrierea semanticii diverselor constructii
sintactice s-au folosit o serie de functii auxiliare, care dau ca rezultat
elemente din monada M, adica din tipul de date M Value. Adesea
acestea sunt folosite ca $i analoagele lor care fac calculul cu valorile
obi$nuite, numai ca acum, rezultatele sunt impinse, transferate cu
functia return (notata uneori unit) in monada, transformate in
procese de calcul care vor da acele rezultate.
in continuare sunt transcrise aceste functii auxiliare:

-- 1 ) ca utarea i n Environment
lookupE : : Name-> Environment-> M Val ue
lookupE x [ J ~ fail (" Variabila far a va l oare : " ++ x)
lookupE x ( (y, b ) :e) = if x•-y then r eturn b else lookupE x e

-- Ope ra tii cu va lori mo nadice


-- 2) aduna rea a doua valori
add .. Value - > Value-> M Value
add (Num i l (Num j l return (Num (i+j ))
add a b = fail (" Nu sunt ambele numere : " ++
showval a ++ "," ++ showval b )

-- 3) applica r ea unei functii

apply (Fun kl a = k a
apply f a = f a il (" Ar trebui sa fie functie : " ++
showval fl

-- Pentru e xecutie

0 introducere in Haskell 98 prin exemple - 176 - DanPopa


test :: Term-> String
test t - showM (interp t [) l
Testarea interpretorului se poate face furnizAnd functiei test termul de
evaluat in context vid. De asemenea se poate folosi showM aplicat
rezultatului evaluMi cu lnterp a termul dorit in contextul dorit.

0-.. dan@RonRon /home/dan/prac!Jca-haslr.el - Konsole - Konsole


File Sessions Settmgs Help

Main> test (App (Lall x (Add (Yar "x") (Yar


(Add (Con 10) (Con 11)))
"Success: 42"
Main) I
ct[ITfll
Un calcul efectuat cu succes de lnterpretor.

Concluzii: Dupa cum se poate vedea din acest exemplu, limbajul Haskell
98 ~i conceptul de monada (din teoria categoriilor) sunt instrumente
puternice pentru studiul interpretoarelor ~i lucrul practic Ia realizarea de
interpretoare. Ceea ce am ilustrat aici este mai curand un "back-end" al
unui sistem care prelucreaza limbajul dar este perfect posibil sa se faca ~i
parsarea in Haskell, fie cu un parser creat automat de un generator, fie cu
unul scris ad hoc, ori cu monada parserelor (primul studiul de caz ).

in capitolul dedicat combinatorilor de parsere vom arata cum se poate


scrie un evaluator de expresii realizand parserul acestuia pas cu pas in
mod incremental. Se folosesc ni~te parsere simple pe care le i mbinam
uneie cu altele cu ajutorul unor combinatori $i ale unor descrieri cu aspect
imperativ facute i n do-notatie. Dar intrucat do-notatia este doar o
macrodefinitie care folose~te pe ascuns o monada dedic urmatorul capitol
prezentarii monadelor, dar tara demonstratiile pe care altminteri un
matematician le-ar fi considerat obligatorii. Capitolul este scris pentru

0 introducere in Haskell 98 prin example - 177 - DanPopa


programatori, ~rora le prezintA example clare de cod extrase din alte
proiecte.

0 introducere in Haskell 98 prin exemple - 178 - Dan Popa


7 Monade utilizate de programatori

Despre: 7 Monadele folosite de catre programatorii ce lucreaza in


Haskell

7.1 .Monada identltate


A$a cum printre functii exista functia identica, cea mai simpla functie,
printre monade exista cea mai simpla monada, monada identitate.
Pentru ca universul monadei sa fie diferit de multimea valorilor
diverselor tipuri (de$i matematicianul ar vedea acest univers ca o
reuniune de tipuri) in Haskell se define$te constructorii (eel de tip $i
eel de date) ca mai jos. Ambii pot avea acela$i nume ld, deoarece
numele pentru constructorii de date $i pentru constructorii de tip se
pastreaza de catre compilatorul/interpretorul de Haskell in doua
spatii de nume diferite:
new t ype I d a = Id a
Va puteti imagina tipul ld (cu argument de tip a) ca fiind format din
capsule pe care scrie ld $i in care se afla elemente de tip a.

0 Introducers in Haskell 98 prin example - 179 - Dan Popa


Multi mea capsulelor este multi mea valorilor monadice.
Am folosit newtype ~i nu data deoarece exista restrictia de a nu
folosi sinonime de tip Ia declaratiile de instante de clasa (Ia unele
implementari). Ori noi dorim sa definim o monada adica o instanta a
clasei Monad.

Urmeaza declaratia prin care i nzestram aceasta multime de valori


monadice cu proprietatea de a apartine clasei Monad. Clasa Monad
avand predefinite semnaturile functiilor "bind" ~i return va trebui sa
declaram doar cum se calculeaza acestea. Declaratiile tipurilor lor
sunt incluse in declaratia de clasa.
instance Monad I d whe re
(>>= ) (Id X) f f X

return = Id
Observati ca ultimul rand se mai putea scrie altfel return y

= Id y dar e suficient sa scriem ca return este totuna cu functia


constructor de date ld.
Bind-ul combina valoarea monadica (ld x) cu functia f i n eel mai
simplu mod posibil: Calculeaza f (x) pentru x-ul scos din capsula.
Monada identitate este utilizata atunci cand incepem sa dezvoltam o
aplicatie ~i inca unele detalii nu sunt stabilite dar deja am inceput sa
scriem cod in do-notatie. Ori do-notatia ca sa ruleze are nevoie de o
monada, fie ea ~~ foarte simpla.

7.2. Monada de UO.


intr-un limbaj functional pur, tara efecte laterale, ordinea evaluarii

0 introducere In Haskell 98 prin exemple - 180 - Dan Popa


functiilor poate fi lasata pe seama optimizatorului inclus in compilator
sau interpreter. Sunt situatii insa c~nd ordinea executiei conteaza.
Este $i cazul operatiilor de 1/0 in care ordinea citirii datelor sau a
scrierii lor este importanta. Cea care ne garanteaza ordinea
executiei in Haskell este scrierea in do-notatie a codului. Datorita
felului cum e definita do-notatia cu apeluri de operatori bind pu$i unul
in altul ordinea este indubitabila. Pentru ca do-notatia sa functioneze
$i pentru operatii de 1/0 a fost nevoie de o monada. Aceasta monada
predefinita este monada de 1/0. Constructorul ei de tip este notat
chiar cu 10. Functiile care fac operatii de citire $i dau rezultate de
tip "a" formeaza submultimea de valori monadice de forma 10 a.
Exemplu:
getChar : : IO Char
lar functia putChar va primi un caracter (Char) $i va returna o
valoare monadica 10 () care se obtine incapsul~nd un tuplu vid ().
putChar :: Char -> 10()
Un mic program principal, o functie main care a$teapta un caracter
$i il tipare$te va fi scris in do-notatie a$a:
main :: IO ()
main = do c <- getChar
putChar c
Notati faptul ca in Haskell, limbaj strict tipizat, functia main este
obligata sa aiba tipul 10(). Ea constituie ca $i in C punctul de intrare
in programele scrise in Haskell. Ea este actiunea executata de
program. Folositi in locul interpretorului hugs interpretorul runhugs.
El va porni imediat functia main a programului incarcat.

0 introducere in Haskell 98 prin exemple - 181 - Dan Popa


Bind~~ return ?
La monada de 10 bind ~~ return au, a~ cum e de a~teptat, tipurile:
>>= : : 10 a-> (a-> 10 b) -> 10 b
return : : a-> IO a

Un exemplu mai complicat de operatie de 10, scrisa in do - notatie,


in care bucla e inlocuita cu recursia ~~ Ia care am pus acolade ~i

punct ~i virgula Ia do-notatie in mod explicit este:


getLine : : 10 String
get Line = do { q <- getChar
if q == ' \n '
then return ""
else do {rs <- getLine
return (q : rs )
Explicatia: q este caracterul citit Ia inceput, daca este final de linie va
rezulta stringul vid altfel va apela recursiv getline pentru a citi restul
liniei rs ~i va returna linia formata din primul caracter q ~i restul rs.
Observati ~~ faptul ca fiecare do realizeaza o secventa de actiuni iar
ramificatiile (de exemplu if-urile) obliga Ia folosirea altui do (sau ca
alternativa mai rara, a unui return).

7 .3. Monada starllor (The State Monad)

Constructorul monadei starilor se noteaza adesea cu SM (de Ia state


monad) dar puteti intalni ~i alte nume (ex: St, de Ia State Monad.)
Numele nu este rezervat. Tipul elementelor monadei se bazeaza

0 introducere In Haskell 98 prin example - 182 - Dan Popa


(aceasta este o noutate I) pe un tip al starilor, anterior definit $i notat
In exemplul nostru cu S. Nu uitati sa-l definiti (mai lnainte). Unele
versiuni de interpreter recomanda sa-l definiti cu "newtype", nu cu
"data" ceea ce va avea ca efect intern crearea unei noi descrieri a
tipului, chiar daca ea mai exista.
newtype SM a= SM (S -> (a,S))
In esenta este un tip cu valori monadice de forma unor capsule. Pe
fiecare capsula de acest tip imaginati-va ca sta lipita o eticheta pe
care scrie SM. Fiecare capsula contine o functie de Ia o prima stare
Ia o pereche valoare - stare. Valoarea poate fi de orice tip (notat cu
variabila de tip a) iar starea rezultata e din acela$i tip ca $i starea de
pornire. Functia din capsula are tipul:
S -> (a , S)
Declaratia prin care lnzestram acest tip cu structura de monada
tacandu-1 element al clasei Monad reprezinta $i ocazia de a defini
operatorii:
instance Monad SM where
(SM cl) >>= f = (\sO -> let (r, sl) cl sO
SM c2 f r
in c2 sl

Nota: indentati obligatoriu randurile cu descrierile operatorilor astfel


incat layout-ul programului sa faca din aceste declaratii un text inclus
In declaratia "instance".
Cum functioneaza bind Ia monada cu stari, explicam In continuare:
Noul element produs de bind este rezultatul compunerii capsulei ce

0 introducere in Haskell 98 prin example - 183 - Dan Popa


contine functia schimba.rii de stare c1 (notata. (SM c1)) care se
combina.tse leaga. de functia f. De aici rezulta. o capsula. (valoare
monadica.) a ca.rui functie interioara. produce tot o pereche $i este
evident functie tot de un argument iar acesta este notat sO. Ce va
face functia din capsula. cu el ? Urma.riti etapele:
a) ii aplica. actiunea, transformarea c1, scoasa. din capsula data..
Aplicand c1 lui sO obtine (nu uitati ca. c1 e o functie de Ia o stare Ia o
pereche stare - valoare I) o pereche (r,s1) ale ca.rei elemente le
numim r- de Ia rezultat $i s1 -de Ia "starea 1"). r e ste o valoare de
un tip notat cu "a" iar s1 e din tipul sta.rilor S deorece in capsule erau
functii cu schema de tip s - > <a , s) .
b) Asupra rezultatului r de tipul (variabil) a se aplica. functia f data. ca
argument lui bind. Argumentul ei este de tip a iar bind are
intotdeauna tipul :
(>>=) :: M a -> (a -> M b) -> M b
unde in paranteza. este chiar tipul lui f. Acest lucru ne permite sa.
aplica.m functia f valorii de tipul a $i sa. obtinem o noua. valoare
monadica., adica. o noua. capsula. (SM c2) cu o alta. functie de
schimbare a sta.rii in ea. Ea are tot tipul s - > (a, s > .
c) Aceasta. functie c2 se aplica. sta.rii s1 rezultate Ia punctul (a)
rezultand o pereche noua.. Rezultatul aplica.rii il nota.m prin urmare
c2 s1; (se cite$te, evident, "c2 aplicat lui s1 ").
d) Functia care per total duce sO in c2 s1 este func~a finala. care va fi
plasata. (incapsulata.) in capsula monadica. rezultat care rezula. din
calculul bind-ului dintre (SM c1) $i f.
Functia care introduce valorile nemonadice in monada. (in capsulele

0 introducere in Haskell 98 prin exemple - 184 - Dan Popa

l
care sunt valorile monadice) este ca lntotdeauna "return".
return k = SM (\ s -> (k , s ))
Ea produce capsule care contin o functie a c~rei transformare,
proiectat~ pe stari, este functia identica. Deci o functie care nu
modifica starea primit~ . dar o lmperecheaza cu o valoare data.
Valoarea rezultata, a$a cum se vede examinand primul element al
perechii, este cea dorita de noi - k.

Practicienii folosesc In plus $i alte functii:


Din dorinta de a prelucra $i starea ca pe o valoare au inventat o
actiune (valoare monad ic~) care aduce starea pe post de valoare.
Au numit-o readSM.
readSM : : SM S
readSM = SM ( \s -> (s , s ))
Din dorinta de a modifica starea curenta (intervenindu-se astfel pe
firul schimbarilor de stare implicite) s-a definit:
updateSM (S -> S ) -> SM S
updateSM f = SM (\s -> ( ( ) , f s))
Observati ca valoarea rezultata (primul element Ia perechii) este
tuplul vid.
Era posibil sa fie nevoie sa se aplice efectiv functia din capsul~ pe o
stare S ca sa vedem ce obtinem. in "A Gentle Introduction to Haskell
98" Ia pagina 46 lntr-un paragraf numit "run a computation in the SM
monad" - "a rula un calcul i n monada starilor" este definit:
runSM : : S - > SM a-> (a , S)
runSM sO (SM c) = c sO

0 introducers in Haskell 98 prin example - 185 - Dan Popa


De notat este ~i urm~toarea analogie: Tmbinarea valorilor monadice
care incapsuleaz~ functii duce Ia obtinerea unei valori monadice
care incapsuleaz~ o functie complex~. compus~. Este ca atunci
c~nd se imbin~ instructiuni imperative (evenual structurate) pentru a
forma o instructiune compus~. complex~: programul. El/ea se poate
apoi rula.
0 generalizare a monadei st~rilor este monada cu st~ri ~i string de
10. Acel s este inlocuit cu o stiv~ iar Ia perechea de ie~ire se adaug~

~i un String, ea devenind o triplet~ :

7.4.Monada cu stiri '' (string) de 1/0 numita in unele lucrirl


"monada cu stari 'i 1/0".
0 versiune a ei lnsotit~ de cod scris in MetaML se poate g~si lntr-o
lucrare ce se poate g~si pe internet: "DSL Implementation using
staging and monads" semnat~ de Tim Sheared, Zine-el-abidine
Benaisa ~i Emir Pasalic numit~ acolo monada StOut. Ea folose~te

un tip anterior definit, Stack, pe post de stare:


newtype M a=StOut (Stack->(a , Stack , String))
e>>=f = StOut
(\n -> let(a , nl , sl)=(unStOut e)n
(b,n2 , s2)=unStOut (fa) nl
in (b , n2 , sl++s2)

unde este nevoie ~i de declaratia lui unStOut, functia care scoate


acea functie interioara din capsul~. (Fiindc~ in definitia lui >>= nu
este scris acel e sub forma (StOut f).

0 introducere in Haskell 98 prin example - 186 - Dan Popa


unStOut(StOut f ) = f

Return-ul monadei unStOut este definit:


return x =StOut (\n -> (x , n ,nn))

Am folosit cu succes aceasta monada in cadrul laboratoarelor de


limbaje formale (care s-au desfa$urat in perioada nov - ian din 2006
incoace Ia Univ. "V.Aiecsandri" din Bacau) cu studentii anului al 11-
lea, Ia realizarea Back-End-urilor interpretoarelor. ~i in continuare in
anii ulteriori ... le vom folosi iar.

7.5.Monada listelor

Este o monada care se folose$te Ia simularea calculelor


nedeterministe. Constructorul polimorfic al elementelor monadei este
eel cunoscut de Ia liste: [ 1
Operatorul bind este definit in monada listelor ca fiind :
( >>=) :: [a) -> (a -> [b) ) -> [b)

Observati ca este doar un caz particular al :


(>>=) :: M a-> (a-> M b) -> M b

Cum functioneaza acest operator:


a) fie data o lista de elemente de un tip oarecare a - ele joaca rolul
valorilor initiale
b) se mai da $i o functie care transforma un element de tip a intr-o
lista de valori de tip b - functia este un calcul nedeterminist oarecare
iar lista de valori de tip b este lista valorilor posibile ale rezultatului.
c) operatorul bind aplica functia pe toate elementele listei initiale
(rezultAnd liste de rezultate partiale) $i apoi concateneaza listele

0 introducere in Haskell 98 prin exemple - 187 - DanPopa


astfel obtinute (pentru a obtine lista final~ format~ din rezultate care
s-ar fi obtinut din valoarea initial~ - nedeterminist~ - prin aplicarea
functiei).
Operatorului de mai sus se poate scrie a~a:
(>>= ) 1 f = concat (map f 1)
unde concat transform~ o list~ de liste in concatenarea cu (++) a
acestora. Deci conca t [ [ 1 , 2 , 3] , [ 4 , 5] ] = [ 1 , 2, 3 , 4 , 5] .
lnjectarea de valori in monad~ se face cu un return care transform~

un element de tip oarecare a i n lista format~ doar din acel element.


Un asemenea return se poate declara imediat, astfel:
r eturn a = [a]
Observatie: Expresivitatea programelor in do-notatie scrise cu
monada listelor ~~ a "list comprehensions" - acele "multimi ordonate
prezentate descriptiv" este, afirm~ alti autori, comparabil~.
Exemplu:
11 = [(x , y) I x <- [1,2 , 3) , y <- [1,2,3], x/= y]
se poate scrie monadic, in do-notatie:
11 =
do x <- [ 1 , 2 , 3]
y <- [1 , 2 , 3]
True <- return (x /= y)
return (x , y)
iar ambele au ca valoare lista:
[(1, 2 ), (1 , 3 ), (2 , 1) , (2 , 3) , (3, 1 ), ( 3 , 2 ))

Exemplul este preluat din "A Gentle Introduction to Haskell 98" unde
figura Ia pg. 45.

0 introducere in Haskell 98 prin example - 188 - Dan Popa


7.6. Monada Maybe

Uneori, in literatura de specialitate, monada Maybe este prezentata


i mpreuna cu o istorisire despre oita clonata. ldeea, in acel context,
era ca oile pot sa aiba "parinti" sau, odata cu aparitia oilor clonate se
poate sa nu aiba "parinti". (eng. "may be" se traduce prin "poate" ).
Povestea ilustreaza scopul principal al monadei Maybe: utilizarea sa
Ia realizarea de calcule cu valori ~i eroare posibila, eroarea fiind
bineinteles cazul cand nu exista rezultatul.
in "A Gentle Introduction to Haskell 98" este exprimata ideea ca
monada Maybe este un caz particular al monadei listelor; cazul cand
listele au un element. Este un bun rezumat.
Un exemplu: elementul Just 7 - corespunde cu lista cu elementul 7
iar Nothing corespunde cu lista vida. lntuiti de aici ca tipul valorilor
monadice (al monadei Maybe} este definit astfel:
newtype Maybe x = Just x I Nothing
iar declaratia instantei clasei Monad este, pentru tipul Maybe:
instance Monad Maybe where
(Jus t x) >>= k k x
Nothing >>= k Nothing
return x Just x
fail s Nothing
Ea serve~te Ia programarea (in do-notatie) a calculelor care pot da
erori sau Ia completarea tipurilor de date cu un Nothing.

0 introducere in Haskell 98 prin exemple - 189 - Dan Popa


7.7. Monada parserelor

Parserele, acele programe care structureaza textul de intrare


transformandu-1 in arbore sintactic pot fi combinate intra ele cu
ajutorul operatorilor bind $i return sau, mai comod, scriind programe
in do-notatie. Se incepe cu ni$te parsere simple a$a cum sunt cele
mai simple parsere din bibliotecile Parselib sau Parsec (aceste
parsere simple corespund eel mai adesea atomilor lexicali din
limbajul de analizat). Acestea se combina asemanator terminalilor $i
neterminalilor care formeaza regulile unor gramatici sau in alte
moduri (de exemplu folosind combinatorii de parsere care genereaza
din parsere vechi altele noi). in final se obtine un parser pentru
intregul limbaj, construit incremental din parsere mici.
Monada parserelor are constructorul de tip declarat adesea astfel:
newtype Parser a=Parser(S t ring -> [(a, String)] )
Observati ca un parser este capabil sa transforme Ia nevoie un string
intr-o lista de analiza sintactice posibile, neterminate, fiecare fiind o
pereche fermata dintr-un rezultat (arbore sau nu) $i un rest din
stringul de intrare, evident tot de tipul String. El incapsuleaza functia
care va face analiza.
0 functie auxiliar~ este folosit~ In practicA pentru a scoate din capsula
functia cu tipul String -> [(a , String) ]$i a o aplica pe stringul
de Ia intrarea parserului:
parse :: Parser a-> String-> [(a,String)]

0 introducere fn Haskell 98 prin exemple - 190 - Dan Popa


p arse ( Parser p ) = p
Declara1ia prin care lnzestram mu~imea valorilor (Parser a) cu
operatorii monadici arata acum astfel:
instance Monad Parser where
return a= Parser (\cs -> [(a , cs)))
p >>= f Parser (\cs -> concat
[p a r se ( f a ) cs '
(a , cs ') <-parse p cs)

Observa1i ca exista o asemanare notabita atat cu monada starilor cat


~i cu monada listelor. Ceea ce conteaza este insa faptul ca verifica
legile monadei (ceea ce matematicienii au demonstrat iar noi nu
demonstram aici) ~i ca serve~te Ia a realiza opera1ii de combinare a
parserelor.
Cum functioneaza operatorul bind ? Combinarea dintre o capsula
Parser ~i o func1ie decurge astfel:
a) Cu parserul p se analizeaza stringul cs (argumentul functiei din
capsula data ca rezultat).
b) Perechile (a,csC) ob1inute se prelucreaza astfel:
- fiecare pereche este despartita In elementele a (valoarea produsa
de parser, adesea e arborele sintactic aici) ~i cs' ( coada string-ului
analizat, adica ceea ce a ramas neanalizat).
- se aplica f asupra lui a $i se obtine un alt parser
- cu el se parseaza cso (adica textul ramas)
c) listele obtinute de Ia aceste parsari se concateneaza
Astfel toate continuarile posibile ale analizei sunt exploatate

0 introducere in Haskell 98 prin exemple - 191 - Dan Popa


prelucrand rezultatele lor cu functia f.

Notati ~i faptul c~ monada parserelor face parte dintr-o cia~ de


structuri algebrice mai puternice fiind o monad~ cu "zerou" ~i

"adunare". Adunarea corespunde alternativei din regulile gramaticale


iar zeroul este o valoare monadic~ ce se comport~ ca un element
neutru Ia adunare. Bibliotecile standard care insotesc implement~rile
Haskell includ o clas~ numita MonadPius declarat~ tocmai pentru
aceste monade.

Observatie: obligatia de a verifica faptul c~ o structur~ algebric~

inclusa in clasa Monad (ca o instant~) este monad~ revine


programatorului ~i se face in cadrul teoriei matematice a lambda-
calculului. Limbajul Haskell nu ofer~ nici un mecanism prin care s~

se verifice automat dac~ structura propusa de programator este o


monad~.

Pe de alt~ parte este absolut necesar ca structura pe care o


declar~m ca instant~ a clasei Monad sau MonadPius s~ verifice
legile monadei deoarece numai a~a do-notatia functioneaz~ in acord
cu intuitia noastr~. educat~ Ia ~coala program~rii imperative. Altfel
rezultatele sunt greu previzibile prin simpla privire a programului cu
ochiul programatorului in limbaje imperative.

0 introducere Tn Haskell 98 prin exemple - 192 - Dan Popa


Studiu de caz: evaluatorul de expresii

Despre: Fe/ul cum putem scrie un evaluator de expresii cu ajutorol


combinatori/or de parsere $i a/ descrierilor fn do-notatie. Se pomeste
de Ia ni$fe parsere simple care se combina obfinandu-se in final un
parser complex capabil sa analizeze o expresie $i sa retumeze
valoarea ei. Asemenea parsere modulare se g~sesc fn biblioteci.

In lucrarea Monadic Parser Combinators publicata in 1996 de


cercetatorii Graham Hutton $i Erik Meijer $i difuzata ca "Technical
report NOTTCS-TR-96-4" al Departamentului de Computer Science
al Universitatii din Nottingham (care a fost disponibila $i Ia
http://www .cs.nott.ac.uk!Department/Staff/gmh/monparsing.ps ) este
prezentata realizarea unor parsere extensibile pas cu pas conform
regulilor unei gramatici. Ca suport algebric al 7mbinarii acestora se
folosea monada parserelor (descendent recursive) iar ca operatori
se defineau o serie de "Parser Combinators" {combinatori de
parsere) intr-un mod care ni se pare inspirat de operatorii definiti Ia
crearea expresiilor regulate din teoria limbajelor formale. Ace$ti
operatori bine ale$i serveau apoi Ia a imbina parserele obtinute intr-

0 introducere Tn Haskell 98 prin exemple - 193 - Dan Papa


un mod care urm~rea gramatica limbajului c~ruia urmeaz~ s~ i se
construias~ un interpreter.
Cercet~rile lor au condus ulterior Ia realizarea unei biblioteci mult
mai complexe de asemenea combinatori de parsere, cum este
biblioteca Parsec. (0 g~s~i In
/usr/share/hugsllibraries!Text/ParserCombinators/ pe sistemul
Mandriva 2005 atunci cand instalati Hugs 98 din distributia
Mandrake 10.0). La Ubuntu Linux este bibliotec~ separat~. instalati
The Haskell Platform.

Vom explica aceast~ tehni~ prin comentarea, din punctul de vedere


a/ realizatorului de interpretoare, a lucr~rii originare a celor doi, (sub
rezerva faptului c~ au ap~rut dezvoltari ulterioare cum ar fi cea
semnat~ de Andrew Partridge ~?i David Wright: Predictive parser
comblnators need four values to report errors din Journal of
Functional Programming 6(2): 355-364, 1996).

Prima dat~ am testat programul din paginile care urmeaz~ pe o


statie Linux (Mandrake 10.0 ~?i Mandriva 2005) pe care instalasem
versiunea din 2002 a interpretorului Hugs 98. La aceast~ versiune
clasa MonadPius este separat~ de biblioteca Prelude (biblioteca
standard) iar clasa MonadZero nu este definit~ (dar se poate folosi
MonadPius In locul ei deoarece contine tot ce era definit In clasa
MonadZero). Fiind plasate lntr-un fi~?ier separat, Monad.hs acesta
trebuie importat explicit:

0 introducere in Haskell 98 prin exemple - 194 - DanPopa


- - - cap-monadic-parsing-03 . hs
--- 1 aug 2005 revazut pe 9 ian 2006
module MonParsinq where
import Monad
Monadic Parsing i n Has ke ll
--- dupa Graham Hutton si Erik Mei jer

De~i in prima versiune a lucr~rii autorii declaraser~ tipul nou cu type


(pentru c~ se utiliza Gofer) in a doua versiune folosesc newtype.
newtype Parser a = Parser (Str ing-> [(a , String )) )
intr-o lucrare anterioar~ autorii explic~ cum au ajuns Ia aceast~
declaratie:
Initial au considerat Parserul: functie care transform~ String in Tree.
t ype Parser String - > Tree
Apoi deoarece parserul poate s~ nu consume cu succes toat~

intrarea au trecut Ia:


type Parser Str i ng -> ( Tree , Stri ng )
Parserul putea incerca in mod nedeterminist diferite moduri de a
parsa intrarea oprindu-se in diverse situatii, dup~ parcurgerea unei
p~rti mai mici sau mai mari din textul de intrare. Toate aceste
combinatii formeaz~ o list~. Pentru a le surprinde autorii decid s~

declare:
type Parser St ring -> [ ( Tree , String )
Diversele parsere (care se vor folosi intr-un sistem bazat pe
combinarea parserelor) pot returna diverse feluri de arbori (ba chiar
arbori cu o ramur~. liste sau elemenet unice, etichete de noduri ori
alte valori). Tree va fi inlocuit cu o variabiiA de tip ceea ce face din

0 introducers in Haskell 98 prin example - 195 - Dan Popa


Parser un constructor de tip parametrizat (polimorfic).
type Parser a = String -> [ ( a , String )

in acest moment Parser este un constructor de tip polimorfic (capabil


sa duca un tip in alt tip, ceea ce il face sa fie un functor in termeni
din teoria categoriilor) $i poate fi considerat primul element -
constructorul de tip - definitoriu pentru monada parserelor.
Adaugand $i un constructor de date dupa semnul egal, care va fi tot
cuvantul Parser se ajunge Ia:
newtype Parser a= Parser (String-> [(a , String)] )

Tipul a , (variabil) va fi, pentru interpretoarele realizate in acest mod,


tipul rezultatului intors de interpreter. Astfel interpretorul ~ de
expresii intregi va avea tipul: Parser lnt , a fiind inlocuit cu Int. Astfel,
prin polimorfismul parametric se rezolva $i problema extensibilitatii
datelor, adica a extensibilitatii colectiei de multimi de valori care sunt
calculate de interpreter. (Mai exact, ele sunt calculate de diferitele
evaluatoare ale diferitelor feluri de constructii sintactice.)

Autorii nu demonstreaza in articolul citat natura monadica a multimii


parserelor, dar, presupunand-o demonstrata, se pregatesc sa
implementeze pe bind (>>=) $i return. in Haskell, monada este
predefinita sub forma de clasa, clasa Monad ($i clasa MonadPius).
Nu mai este nevoie deci sa includeti in program declaratia clasei
Monad:
class Monad m where
return a -> m a
(>>=) m a -> (a -> m b) -> m b

0 introducere fn Haskell 98 prin exemple - 196 - DanPopa


Declaratia afirm~ c~ fiecare instant~ din aceast~ clas~ are un
constructor de tip (notat generic cu m- ca o variabil~) $i dou~ functii
polimorfice return (uneori numit~ $i unit) $i >>= (se pronunt~ "bind").
Pentru a declara o instant~ a unei clase In Haskell trebuie s~

preciz~m exact comportamentul functiilor, deoarece doar


semn~turile lor sunt date in declaratia clasei. Acest lucru se face cu
o declaratie instance.
instance Monad Parser where
return a Parser (\ cs -> [ (a, cs) ) )
p >>= f = Parser ( \cs -> concat
[parse (f a) cs2 I
(a , cs2) <-parse p cs])

inaintea acestei declaratii, (pe care am explicat-e In capitolul despre


monade) trebuie s~ definim $i functia "parse" care scoate functia
realizat~ de parser (aici p) de sub constructorul de date Parser.
parse (Parser p) = p

Parsarea realizat~ de parserul (Parser p) este realizat~ chiar de


functia inclus~ In acel parser. Datorit~ felului cum functioneaz~

monada parserelor, constructia de parsere creeaz~ parsere cu functii


asociate din ce In ce mai complexe, functii care trebuie apoi puse In
functiune, exploatate, puse s~ analizeze text. Este ceea ce face
aceast~ functia parse (cu minuscul~). in etapa test~rii programului
veti scrie: parse <nume parser> <String de analizat>
A$a cum se $lie din paginile precedente despre monade, return
transform~ o valoare dinafara monadei incapsul~nd-o lntr-un
element din monad~.

0 introducere in Haskell 98 prin example - 197 - Dan Popa


La monada parserelor return are rolul de a transforma o valoare in
parserul care returneaza acea valoare tara sa mai proceseze nimic
din ~irul de intrare. Folosind return vom preciza parserului compus
scris in do-notatie dupa ce formula de calcul se obtine valoarea
retumata (din valorile asociate elementelor parsate). Acest return se
folose~te adesea intr-o serie imperativa de instructiuni scrise in do-
notatie, ca o ultima instructiune ce calculeaza ~i returneaza (adica
incapsuleaza). rezultatul final al interpretarii. Operatorul >>=
serve~te Ia conectarea unul dupa altul a parserelor, dar in programe
nu-l vom vedea prea des fiind ascuns in do-notatie.

Fundamentele teoretice: Autorii atrag atentia asupra legilor monadei


care trebuie sa fie satisfacute de cele doua operatii, return ~i bind
(>>=): return a »= f =fa
p >>= return =p
p »=(\a-> (fa»= g)}= (p >>= (\a ->fa))>>= g
Legile afirma ca return este element unitate Ia st~nga $i Ia dreapta
pentru bind (>>=) ~i ca >>= este asociativa. Ele vor fi cele care
garanteaza functionarea do-notatiei intr-un mod conform cu intuitia
programatorilor care au folosit programarea structurata in C.
Un parser, scris sub forma de inlantuire de bind-uri va arata astfel:
p1 >>= \a1 ->
p2 >>= \a2 ->

pn >>=\an->
f a1 a2 ... an

0 introducere in Haskell 98 prin exemple - 198 - Dan Popa


Functionarea sa: se aplica p1 $i se obtine rezultatul a1 care se da
parserului p2 iar rezultatul se va numi a2 $i a$a mai departe iar
ultimul rezultat an va servi Ia calculul final a ceea ce notam uzual
prin f(a1 ,a2, ... an). Punctele "... " nu fac parte din Haskell.
Acela$i parser poate fi acum scris In do-notatie $i arata astfel:
do {a1 <- p1 ;
a2 <- p2

an<- pn
f a1 a2 ... an} -- fiind evident mai U$Or de citit
El se se poate scrie $i unidimensional, pe o singura linie de forma:
do {a1 <- p1 ; a2 <- p2 ; ... ; an <- pn ; f a1 a2 ... an}
Oeoarece valoarea rezultata trebuie sa fie tot o valoare monadica, In
cazul c~nd functia f nu returneaza o valoare monadica, valoarea va
trebui injectata In universul monadei cu return, ceea ce face ca
parserul sa arate a$a:
do { a1 <- p1 ;
a2 <- p2 ;

an<- pn ;
return (f a1 a2 ... an) }
~i acesta se poate scrie unidimensional, pe o singura linie de forma:

do {a1 <- p1 ; ... ; an <- pn ; return (f a1 a2 ... an)}

Example din practica: un parser pentru un $ir de digiti care


retumeaza valoarea numarului sub forma unei valori monadice care

0 introducere in Haskell 98 prin exemple - 199 - Dan Popa


incapsuleaza un numar intreg:

digiti = do {p <-digit
1 <- many digit
return (foldl (\a b -> lO*a+b) 0 (p:l))

unde parserul pentru un digit (o citra} e definit ca mai jos ~i produce


valoarea incapsulata a digit-ului:

digit =do{x <-token (sat isDigit );


return (ord x - ord ' 0 ' )}

Alt exemplu: Constructia unui parser care parseaza trei itemi


(caractere} ~i returneaza perechea fermata de primul ~i al treilea:

p :: Parser (Char , Char)


p = do { c <- item ; item; d <-item ; return (c,d) }

Combinarea parserelor: Pentru a face mai u~or operatii cu parsere


este util ca ele sa formeze o structura algebrica cu cAt mai multe
proprietati. Un exemplu de structura mai bogata in proprietati decAt
simpla monada este monada cu "adunare" ~i "zerou". in bibliotecile
care i nsotesc interpretorul Hugs monada cu "zerou" ~i "plus" este
declarata in fil?ierul Monads.hs ca o clasa derivata din clasa Monad:
class Monad m => MonadPius m where
mzero :: m a
mplus :: m a-> m a-> m a

0 introducere in Haskell 98 prin exemple - 200 - Dan Popa


lmplementarea unei instante a clasei descrie cum vor fi instantiate
mzero $i mplus. Monada m va fi Monada Parser, (m se lnlocuie$te
cu Parser).
instance MonadPlus Parser where
Parserul zero este
element neutru la operatii cu parsere. El este
este parserul care rateaza intotdeauna analiza
mzero =Parser (\cs -> [])
Functionarea in paralel a doua parsere
adunate cu mplus e data de " parserul suma "
care pentru aceeasi intrare cs da ca raspuns
concatenarea listelor de rezultate
p ' mplus ' q =Parser(\cs-> parse p cs ++ parse q cs)

Este evident ca orice parser urmat de parserul care da lntodeauna


fail va forma un parser care da intodeauna fail (adica lista vida m.

in monada Parser din clasa MonadPius operatorul de adunare a


doua parsere functioneaza a$a: Suma a doua parsere este un
parser care parseaza $irul dat cu ambele parsere $i concateneaza
listele cu rezultate. (++) este operatorul de concatenare a listelor din
Haskell.
De aceste operatii cu parsere este nevoie pentru a realiza in mod
adaptabil parserul I interpretorul asociat unei gramatici pentru care
se $tiu regulile semantice compozitionale de evaluare a rezultatului.

Nota: Practic operatiile cu · mplus · vor servi Ia imbinarea

0 introducers in Haske/198 prin example - 201 - DanPopa


r

componentelor iar imbinarea efectelor acestor componente se va


face cu operatorul bind(>>=).

In practica suntem interesati adesea doar


de primul rezultat al analizei
Acest +++ pune in functiune in paralel ambele
parsere si returneaza primul rezultat impreuna
cu textul corespunzator ramas neanalizat
sau [] daca ambele au dat fail
(+++ ) Parser a -> Parser a -> Parser a
p +++ q = Parser (\cs ->case parse (p 'mplus' q) cs of
[l -> [)
(x: xs) -> [x )

In lucrarea lor, autorii indica o serie de proprietati algebrice $i pentru


operatorul +++.

C~teva parsere care se vor combina: Primul parser dat ca exemplu


este imm. parserul care consuma primul caracter (pars~ndu-1 cu
succes) daca $irul de intrare este nevid $i da insucces (lista vida de
raspunsuri) altfel.
--- Parserul care consuma un sinqur caracter
item Parser Char
item Parser ( \cs -> case cs of
"" -> [)
(c:cs) -> [ (c , cs) ) )

Se observa ca in acest caz "a" din declara~a "Parser a" a fost

0 introducers rn Haskell 98 prin example - 202 - Dan Popa


inlocuit cu Char deoarece acest parser returneaz~ o list~ de perechi
(Char, String). cs - este stringul primit ca parametru. Dac~ e vid sau
$irul de parcurs s-a terminat se returneaz~ fail, adic~ lista vid~ u.
Dac~ e string cu eel putin un prim caracter "c" $i o coad~ "cs" se
returneaz~ lista format~ din unica pereche (c,cs) semnal~nd c~ a
parcurs pe "c" $i a r~mas neparcurs "cs", iar aceasta este unica
parsare posibil~.

Exemplu: Acceptarea reu$it~ a primului simbol din $ir, oricare ar fi el.


Valoarea rezultat~ din procesarea, (interpretarea), acestuia este
chiar acel simbol, elementul dint~i al perechii.

Session Edit VIew 8oolcmarks Settings Help


HonPa rs1ng>
HonParsing> : t i t em
i tem : : Pa rser Char
HonPars1ng> parse i t em ·sclipeste"
I I 's' . "c \ipeste• I I
l HonPa rsing > I

Parserulltem accepti prlmul caracter 'llasi restul.

Exemplu: Adunarea cu ' mplus' a dou~ parsere illml. Se observ~ (in


imaginea urm~toare) cele dou~ rezultate, aici identice: [('d',"an"),
('d',"an")]. Lista de r~spunsuri semnific~ existenta a dou~ pars~ri

(analize sintactice) posibile, care au fost realizate de cele dou~

parsere item. in practic~ se "adun~" de obicei parsere diferite iar lista


analizelor posibile cuprinde perechi diferite.
in schimb adunarea cu +++ va produce un parser determinist care
va da ca rezultat intotdeauna ori o l ist~ cu un singur element ori lista
vid~ .

0 introducere rn Haskell 98 prin exemple - 203 - Dan Popa


Q-~ <1an@iora1host thomf> tdantprac t1c_a h.J'.>~C'I ShP

Session Edit View Bookmarlcs Settings Help

HonParsing> ·~
HonParsing>
MonParsing> parse (item "mp\us · iteml "d~n·
I ( · d' . "an• l. ( · d • . ·an • l I
MonPa rsing>
MonParsing> I ~
~ ~Shell

Doui parsere Item lucrind in paralellsm slmulal

Exemplu: Adunarea cu +++ a doua parsere item elimina


nedeterminismul Se observa ca nu mai apar cele doua rezultate:
(('d',"an"),('d',"an")). Rezulta o lista cu un singur element :(('d',"an")):

Session Edit Vtew Bookmarlcs Settings Help


MonParsing>
HonParsing>
HonParsing>
HonParsing> parse (item +•• item) "dan•
[ ( "d" . "an• l I
MonParsing> I ~

Aceasta adunare de parsere serv~te Ia a


implementa altematlva din regullle gramatlcll.

Dezavantajul parserului item este ca nu poate refuza ceva $i prefera


altceva. Dar se poate defini un parser sat <pred> care primind un
operator boolean va accepta exact acele caractere care fac ca
predicatul <pred> sa fie adevarat.

--- Parserul item avanseaza neconditionat,indiferent ce


intalneste , dar parserul de mai jos foloseste un
predicat, p, pentru a testa caracterul inainte sa-l
consume .

0 introducere in Haskell 98 prin exemple - 204 - Dan Popa


sat ( Char -> Boo! ) - > Parser Char
sat p = do { c <- item; if p c then return c
else mzero)
Exemplu: ('3' ==) este definit pe Char -> Bool. Deci putem defini un
parser care accept~ doar un prim caracter '3' sub forma: sat ('3' ==).

Session Edit Vtew Bookmarks Settings Help


HonPa rsLng> : t (' 3 ' •• 1
( • 3 ~ > : : Char · > 8ool
HonParstng> . t sat ( ' l '•• l
sat (' 3 ' ~> :: Parser Char
Hon~ar~tn g> parse !~at ('3 ' -•11 ' 3 45 '
I I· 3', '45' I I
MonPa rstng> parse (sa t 1'3 ' ••)) ' 456 '
II

1 ,~ • Shel

Parserul sat acceptlnd doar un caraeter anume ('3').

Dezavantajul este c~ suntem obligati s~ scriem predicatul cu tot cu


operatorul de comparatie ==. Ar fi mai comod s~ indic~m exact
caracterul vizat de egalitate. Asa c~-1 definim pe char.

Session Edit V•ew Bookmarks Settings Help


HonParstng> : t char
char :· Char ·> Par ~ ~r Cha r
HonPars1ng> : t char a ·
char •a • :: Parser Char
HonPars1ng> pars e !char ' a ' I •accepta t •
I l'a ' . ·cceptat• I I
MonPars 1ng> pa rse (char 'a' J •nu 1ncepe cu a•
()

• shen

Parser pentru un caracter anume lndlcat.


Este un parser cu predicat la care conditia e sa
fie satisfacut predicatul " a fi egal cu c" , (c = )
char :: Char - > Pa rser Char
cha r c = sat (c == )

0 introducere fn Haskell 98 prin exemple - 205 - Dan Popa


Exemplu: Putem astfel defini un parser care accept~ doar un prim
caracter 'a' descriind parserul sub forma: char 'a'.
Dezavantaj: Aceste parsere recunosc doar un singur caracter, nu un
string. Urm~toarele parsere vor recunoa$te un string. Deoarece in
Haskell string-ul este o list~. definitia folose$te o recursie.
string : : String-> Parser String
string "" return ""
string (c : cs) = do { char c ; string cs ; return (c : cs) }
Exemplu: Putem defini un parser care accept~ doar un text care
incepe cu un anumit string $i respinge orice altceva.

Sesston Edit View Bookmarks Settings Help


'MonPars1ng>
HonPars1ng> :t s t r1ng
'stri ng:: String·> Parser Str1ng
HonParsing> parse (string 'ala' ) · alabala'
I ('ala'. 'bata· I I
MonPars1ng> parse (st r ing 'ala' ) 'altc eva•
()
MonParsing> I
jro
r
.-] Shell I
r

Parser pentru un string anume lndlcal

Dezavantaj: Cu parserele de pan~ acum se pot defini interpretoare


doar pentru limbaje care contin texte de lungime finit~. Cum
limbajele de programare admit s~ se scrie programe oricat de lungi
rezult~ c~ sunt necesare $i altfel de parsere, cu repetitii.
Parserele realizate folosind combinatorul man¥ functioneaz~ prin
aplicarea repetat~ a unui parser anterior creat. Many poate fi
considerat un veritabil combinator de parsere. Cu ajutorul lui se pot
crea (anumite) parsere cares~ accepte texte oricat de lungi.
--- Procesare cu aplicarea repetata a parserului p

0 introducere Tn Haskell 98 prin exemple - 206 - Dan Popa


ma ny : : Parser a -> Pa r s er [a]
many p = many l p +++ r e turn []
ma nyl :: Parser a - > Par ser [a ]
manyl p = do { a <- p ; as <- many p ; retu rn (a : a s)}

Exemplu: Putem defini, folosind $i +++, un parser care accepta doar


textele care rezulta ca succesiune de texte parsate de alt parser. in
exemplu, parserul ale carui texte acceptate se repeta este parserul
alternativei de a fi unstring sau altul: (string "aa")+++ (string "bb")

Session Edit View Bookmal1cs Sett1ngs Help


HonParsing> {Int errup t ed!}
HonParsing> : t many
many : : Parse r a· > Pars er (a)
HonPa rsing> pa rs e (manylls t r t ng • aa"l ••• (s t rtng "bb"))) · aabbaabb"
I ( I · aa·, "bb" , • aa·. "bb"l. • • l I
HonPa1 s ing> pa1se (many( lstn ng •aa"l • .. (St llng "bb"l l l ·aaaabbbb"
I ( l " aa •, •aa•, "bb", "bb"l. .. l I
HonPa rs ing> I
l.-n...- II) Shell
- -1

Parserulllmbajulul atasat expreslel ("aa" sau "bb"), Iterate.

Dezavantaj (minor): Rezultatul evaluarii nu ignora niciuna dintre


valorile obtinute In timpul analizei. Exista totu$i situatii (if I) In care
interpretarea unei structuri sintactice ignora valorile asociate unora
dintre componentele sintactice. Pentru a parsa sau interpreta
asemenea structuri sintactice In care unele substructuri sunt
parcurse $i apoi ignorate e nevoie de alte combinari ale parserelor.

0 alta combinare cu repetitie este realizata cu · sepby' .


--- Parsarea prin aplicarea repetata a parserului p,
" separata" prin aplicarea parseru1ui sep , al carui
rezultat se arunca . Va fi folosit(a) la expresii, unde
termul si factorul sunt sume respectiv produse oricat

0 introducere fn Haskell 98 prin exemple - 207 - DanPopa


de lunqi

sepby :: Parser a-> Parser b ->Pa rse r (a]


p ' sepby ' sep = (p 'sepbyl ' sep +++ return ( ]
sepbyl Parser a -> Parser b -> Parser (a]
p ' sepbyl ' sep = do a<- p
as <- many (do {sep p })
return (a : as)

Observati c~ se aplic~ intAi parserul p, obtinandu-se a, apoi urmeaz~

pars~ri repetate cu secventa de parsere "sep ; p" . Datorit~

semnificatiei lui ";" din do-notatie, (care este definit cu >>) precum $i
faptului c~ >> este definit in clasa Monad din Prelude ca un bind
care ignor~ valoarea de Ia prima procesare ( m >> k = m >>= (\_
->k)), lista as contine dear rezultatele proces~rilor f~cute de p-uri.

Session Edit View Bookmarks Sett ings Help

MonPArSI ng>
HonP<~r s i ng>
HonPars i ng>
MonPars ing>
MonPArs lng> parse !!strtng 'AA'l s epby (s t ring "bb' ll •aabbaa•
( (('AA', •aa' ),"))
HonPars ing> parse ((s t ring •aa' l s epby (string 'bb' )) •aabcaa•
I ((•aa'), 'bcaa• ) I

,-o .. Shell
'-r- ---r-
Analiza unul 'lr de aa-url separate prln bb-url (dol ,1 resp. unul).

Dezavantaj: La interpretarea expresiilor , interpretarea operatorului


trebuie aplicat~ interpret~rii operanzilor (oricati ar exista ei in acel
termen sau factor) , ceea ce oblig~ Ia scrierea altui combinator:
-- Parsare-interpretare prin aplicarea repetata
--- a parserului

0 introducere in Haskell 98 prin example - 208 - Dan Popa


-- - p, separata prin aplic.a rea parserului op , al carui
rezultat este un operator care se presupune ca este
asociativ la stanga si care este folosit pentru a
combina rezultatele de la parserele-interpretoare p.
chain! .. Pa rser a-> Parser (a-> a-> a ) ->a-> Parser a
chain! p op a = (p 'chainll · op) +++ return a
chainll .. Parser a -> Parser (a - > a -> a ) -> Pa rser a
p 'chainll · op = do { a <-p rest a)
where
rest a - (do f <- op
b <- p
rest (f a bl } )

+++ return a

Acest combinator va fi folosit Ia crearea parserului (sau


interpretorului) pentru o expresie definitA ca sumA de termi ~i a
aceluia pentru termi, (termul fiind vazut ca produs de factori).
Deoarece lntr-un limbaj cuvintele limbajului sunt separate prin
blancuri, mai sunt necesare cateva parsere ~i combinatori de
parsere. Ele rezolvA probleme care In mod normal cad In seama
analizei lexicale (eliminarea spatiilor, tab-urilor etc).
Combinatorii lexicali :
Acestia sunt folositi pentru a rezolva problema ale
analizei lexicale .
Parserul pentru spatii, tab-uri si ENTER-uri
space :: Parser String
space = many (sat isSpace)

Parserul pt parsarea unui atom lexical


(folosind parserul p)
care elimina orice spatiu succesor atomului

0 introducere fn Haskell 98 prin exemple - 209 - Dan Popa


t o ke n : : Parser a -> Parser a
t o ken p = d o { a<-p ; space return a}
--- Parseaza un atom lexical (urmat de spatii !!)
s ymb String -> Parser Str i ng
s ymb cs = to ken (stri ng cs )
--- Applica un parser p aruncand orice spatiu dinainte
a ppl y : : Pa r se r a-> Str i ng- > [ (a , Str i ng )]
a ppl y p = parse (do {space ; p ))

Folosind ace~ti combinatori $i primele parsere simple se poate


construi imediat un interpreter pornind de Ia o gramatica. lata cum :

(- Iluatarea aolut~ei

Fie gramatica
expr : : a exp addop term term
term : : c term mulop factor factor
factor : : = diqiti I (expr)
diqiti : : = diqit I diqi t diqi ti
diqit : := 0 I 1 2 I ... I 9

addop + I -
mulop : := * I
-)
Foloaind combinatorul cbainll pt a implement&
recuraia atanqa pentru expr ai term aceaata qram.atica
poate fi traduaa direct intr- un proqram Haskell care
parcurqe expreaii cu numere de o cifra ai le
evalueaza la valorile lor intreqi .

Ca ex . evaluati expreaii cu numere :


MonParainq> parae expr "(10+20)*(50+50)"
--- [(3000,""))

0 introducers in Haskell 98 prin example - 21 0 - DanPopa


expr .. Parser Int
addop .. Parser (lnt -> Int -> Inti
mulop .. Parser (lnt -> Int -> Int)

expr
term
-- term ' chainll ' addop
factor 'chainll · mulop

factor • digiti+++ do{ symb " (" ; n <- e xpr ; symb " ) " ; return n}

digiti • do
p <-digit
1 <- many digit
return (foldl (\a b -> lO ' a+b) 0 !p: 11)

digit • do { x <-token (sat isDigit); return (ord x- ord ' 0 ' ) 1

addop • do {symb "+" ; return (+)I +t+ do {symb "-" ; return (-) I
mulop • do {symb ; return ( • ) I +++do {symb " / " ; return (div) I

lmplementarea ~i testarea

lnterpretorul Hugs 98, al limbajului Haskell 98, a fost fost folosit


pentru a rula programul de mai sus. (GHC 6.2, sau 6.4- Glasgow
Haskell Compiler este de asemenea de luat In considerare.)
Sistemul de operare a fost un Mandriva Llnux 2005.

Rezultatele testarii pot fi v~ute In imaginea urmatoare:

0 introducere in Haskell 98 prin example - 211 - Dan Popa


Sessoon Edtt View Bookmarks Settrlgs Help
Type : 7 for help
HonParstng> parse expr • ( 10+201 ' (50•50)•
I ()600, ·· I I
HonParstng> parse expr ' ( 1+2) ' (5•5 ) '
I Do. ••) I
HonParstng> I
~ ,. Shell
i'=r---
Evaluatorul expreslllor tn actlune.

Avantaj: Adaptarea unui interpreter in cazul aparqiei modificarilor


este imediata deoarece regulile semantice (denotationale) asociate
regulilor sintactice se transcriu foarte U$Or in Haskell dupa modelul
de mai sus, baz~ndu-ne pe ideea:

Idee: Daca o structura sintactica are componentele (recunoscute de


parserele p1 ... pn) care se evalueaza Ia valorile a1 ... an iar
semantica intregii structuri se calculeaza ca o functie f a1 ...an,
implementarea va fi aproape intotdeauna (o exceptie if-urile):

do { a1 <- p1 ;
a2 <- p2 ;

an <- pn ;
return (f a1 a2 ... an) }

Daca regula gramaticala este realizata cu simbolul altemativei "I" se


folose$te operatorul +++ pentru combinarea parserelor ori
interpretoarelor corespunzatoare alternativelor existente.
Nota: lmplementatorii de aplicatii complexe pot folosi, intr-un mod

0 introducere fn Haskell 98 prin example - 212 - Dan Popa


asemanator, o biblioteca mai completa de parsere $i combinatori,
cum este Parsec.

€xemplele anterioare explicate:


Exemplul1.
digiti = do
p <-digit
1 <- many dig it
return (fo ldl (\ a b -> lO *a +b) 0 (p: l ))

lnterpretorul "digit" parseaza primul digit $i obtine valoarea p.


lnterpretorul "many digit" parseaza primul digit $i obtine lista
valorilor, /. Cu operatorul cons (:) din Haskell se obtine lista (p:l) a
tuturor digitilor.
Cu foldl (\ a b -> lO*a+b) o se transforma lista cifrelor in
valoarea numarului format din digitii respectivi .

Exemplul2:
digi t= do{ x <-token (sat isDigit) ;
return (ord x- ord ' 0 ') }

lnterpretorul token (sat isDig i t) produce ca rezultat x chiar


primul caracter (care trebuie sa fie o cifra deoarece el satisface
predicatul isDigit) iar cu formula (ord x - ord '0' ) se
calculeaza valoarea numerica a acestuia care este returnata ca
valoare monadica. Astfel se obtine interpretorul care da ca rezultat
valoarea digitului.

0 introducere in Haskell 98 prin example - 213 - Dan Popa


Sursa lntregului exemplu este reprodusa In continuare:

cap-monadic-parsing-03 . hs- 1 aug 2005 revazut pe 9


ian 2006 v3
module MonParsing where
import Monad

Monadic Parsing in Haskell


dupa Graham Hutton si Erik Meijer

newtype Parser a Parser (String-> [(a , String) ] )

--- Parserul care consuma un singur caracter


item Parser Char
item Parser ( \cs -> case cs of
-> []

(c : cs) -> [ (c, cs)

parse (Parser p) =p
instance Monad Parser where
return a Parser (\cs -> [ (a , cs) ] )
p >>= f = Parser
(\cs -> concat
[parse (fa) cs2 I (a , cs2) <-parse p cs])

instance MonadPlus Parser where


Parserul zero - element neutru la operatii cu parsere
este parserul care rateaza (fail ) intotdeauna analiza
mzero = Parser (\cs - > [))
Functionarea in paralel a doua parsere e data de
"parserul suma " care pentru aceeasi intrare cs da

0 introducere in Haskell 98 prin exemple - 214 - DanPopa


--- ca raspuns concatenarea llstelor de rezultate
p mplus q a Parser (\cs -> parse p cs ++ parse q cs)

-- In practica suntem interesati de primul rezultat


-- al analizei . Acest +++ pune in functiune in paralel
-- &JIIIbele parsere si returneaza primu~ rezult&t i.lllpreuna
-- cu textul corespunzator raaaa neanalizat
-- aau [ 1 d.&ca &JIIIbele au d&t fail
(+++) :: Parser a -> Parser a -> Parser a
p +++ q - Parser (\cs ->case parse (p mplus · q) cs of
I I -> I I
(x:xs) -> (x) )

Parserul item avanseaza neconditionat, indiferent


ce intalneste, dar .. .
parse~ de mai joa foloaeste un predicat pentru
--- a verifica daca • OK si poate aa consume,
aau • momentul aa aemna~eze nepotrivire
sat .. C Char -> Boo! ) -> Parser Char
sat p • do { c <- item; if p c the n return c else mzero}

Parser pentru un caracter dat


• parser cu predicat la care conditia • sa se satisfaca
predicatul (c =)
char .. Char-> Parser Char
char c • sat (c ·-)

--- Parser pentru un string


string . . String-> Parser String
string "" - return ''"
stnng (c : cs) - do { char c ; str1ng cs ; return (c:cs) 1

--- Paraare cu aplicarea repetata a parserului p


many :: Parser a-> Parser [a)
I"
0 introducere in Haskell 98 prin example - 215 - DanPopa
many p - manyl p +++ return [ ]
man y l :: Parser a-> Parser [a]
manyl p - do { a <- p; as <- many p ; return (a:as)}

Parsarea prin aplicarea repetata a paraerului p ,


separata prin aplicarea parserului sep , al carui
rezultat se arunca .
Va fi folosit la expresii , uncle termul ai factorul
aunt aume respectiv produse oricat de lunqi
sepby :: Parser a-> Parser b ->Parser [a]
p 'sepby' sep = (p 'sepbyl ' sep +++ return []

sepbyl :: Parser a-> Parser b ->Parser [a]


p 'sepbyl ' sep =do a< - p ;
as <-many (do (sep p})
return (a: as) }
Parsare-interpretare prin aplicarea repetata
a parserului p,
"separata" prin aplicarea parserului op, al carui
rezultat este un operator care ae presupune ca este
asociativ la stanqa si care este folosit pentru a
combina rezultatele de la p parsere-interpretoare

chain1 : : Parser a -> Parser ( a-> a-> a) -> a -> Parser a


chainl p op a = (p 'chainll · op) +++ return a
chainll .. Parser a-> Parser (a-> a-> a) -> Parser a
p 'chainll · op = do { a <- p ; rest a}
where
rest a (do I f <- op
b <- p
rest (f a b)) }
+++ return a

--- Combinatorii lexicali:

0 lntroducere in Haskell 98 prin example - 216 - Dan Popa


Aceatia aunt folo aiti pentru a rezolva probleme ale
anali zei lexical•

--- Paraerul pentru apatii , tab- uri ai ENTER-uri


space .. Parser String
space - many (sat isSpace)

Paraerul pt paraarea unui atom (foloa i nd paraerul p)


ai eli.mi.na orice apatiu urmator
token . . Parser a-> Parser a
to ken p - do ( a<-p ; space ; return a)

--- Paraeaza un atom aimbolic (urmat de apatii bineni ntelea)


symb . . String-> Parser String
symb cs - to ken (string cs)

--- Applica un parser p arunc and oric e apatiu dinainte


apply .. Parser a -> String-> ((a , Strinq) I
apply p - parse (do (space ; p ))

(- Iluatarea aolutiei
Fie gramatica
expr .. - exp addop term I term
term .. - term mulop factor I factor
factor .. - digit I (expr)
digiti .. - digit I digit digiti
digit .. - o I 1 I 2 I . .. I 9

addop .. - + I -
mulop .. - * I I
-)

--- Foloaind combi natorul chainl l pt a implement&

0 introducers in Haskell 98 prin exemple - 217 - Dan Popa


--- reeuraia a tanga pentru •expr' si 't.rm' . Aceaata
--- gramatic. poat. fi tradusa ctirect intr-un program
--- Haakell c.re parcurge expresii
--- ai le evalueaza la valorile lor intregi .

--- Evaluati expreaii cu numere :


--- MonParai.ng> parae expr " (10+20)*(50+50)"
--- [ (3000, "")]

expr :: Parser Int


addop .. Parser (Int -> Int -> Int)
mulop :: Parser (Int -> Int -> Int)

expr ~ term ·chainll · addop


term = factor chainll · mulop
factor = digiti +++
do I symb " (" ; n <- expr; symb " )" ; return n}
digiti = do
p <-digit
1 <- many digit
return (fold! (\a b -> lO*a+b) 0 (p: 1))
digit = do { X <- token (sat isDigit);
return (ord x - ord ·o· > J

addop =do {symb "+" ; return (+)}


+++ do {symb "-" ; return (-) }
mulop = do {symb "*" ; return (*))
+++ do (symb " / "; return (div))

--- Nota : 0 biblioteca standard de cOIIIbinatori pentru parsere


--- eate biblioteca Parsec .
--- Pe sistemul Mandriva 2005 cu pe care am instalat
--- hugs 98 din distribu}ia Mandrake 10.0

0 introducere in Haskell 98 prin example - 218 - Dan Popa


(adica hugs98 - CD3/3 si biblioteca ceruta
libreadline.so.4 - CDl/3)
gasim usor cu find biblioteca standard Parsec.hs in :
/usr/share/hugs/lib/exts/
/usr/share/hugs/libraries/Text/ParserCombinators/
/usr/share/hugs/oldl~b/

0 introducere rn Haskell 98 prin example - 219 - DanPopa


0 introducere fn Haskell 98 prin example - 220 - DanPopa
l
Studiu de caz: generatorul de cod

Despre: Felul cum putem scrie un generator de cod cu ajutorul


monadei starilor $i descrierilor in do-notafie. lnteresant este mai ales
c8 (intrucat Haskell este un limbaj cu evaluare amanata - lazy
evaluation) nu mai este nevoie de acea "peticiren inapoi - numita
Backpatching in cursuri/e de specialitate.

Ca o prima idee - monada cu stari este (din punct de vedere


algebric) foarte asemanatoare cu monada de 10 $i constituie cadrul
In care se poate programa lntr-un stil aproape imperativ. in acest
capitol am folosit monada cu stari (In engleza the state monad)
pentru a programa un generator de cod inspirat de eel din volumul
profesorului A.A.Aaby- Compiler Construction using Flex and Bison,
tradusa $i Ia noi [Aab-05].
Totu$i programul rezultat $i prezentat mai jos nu este o simpla
transcriere. In limbajele imperative uzuale- cum este C-ui - ordinea
de executie a instructiunii programelor este stricta, rigida, ceea ce
inseamna ca Ia compilarea unui if - de exemplu - nu putem scrie
adresa unde va face codul saltul spre ramura else lnainte de a

0 introducere in Haskell 98 prin example - 221 - Dan Popa


termina de compilat ramura then. in practica realizarii compilatoaelor
in C - se sare peste JUMP NZ -ul neterminat, se memoreaza pozitia
lui se compileaza ramura then se adauga un GOTO Ia sfar$it (unde
? - iar nu se $tie - deci iar va fi nevoie de backpatching) se afla
adresa unde incepe ramura then $i abia acum se poate pune primul
petec. La tel $i pentru al doilea salt. Amintiti-va ca un if nu exexuta
niciodata ceea ce are pe ambele ramuri (then si else) deci dupa
terminarea ramurii then exista cu siguranta un salt (GOTO adresa) Ia
o adresa de dupa instructiunea if.
Aceste reveniri pentru backpatcing fac generatorul de cod al unui
compilator destul de dificil de inteles, mai ales pentru studentii care
parcurg prima oara un astfel de curs.
in Haskell in schimb- nu exista aceasta problema- fiind un limbaj
cu evaluare amanata (lazy evaluation) el va amana in mod natural
completarea instructiunilor din cod a caror adrese de salt nu se
cunosc chiar daca noi scriem programul ca $1 cum ele s-ar cunoa$te.
Practic Haskell ne permite sa strecuram in instructiunile generate Ia
un moment dat valori care par calculate in viitor - iar de ordinea
reala a calculelor Haskell se ocupa singur, intr-un mod transparent
pentru utilizator. Deci programatorul nu-si va mai bate caput cu
backpatching-ul.

9.1. Doar doua vorbe despre lazy evaluation


V-ati intrebat vreodata de ce exista programe de calcul tabelar cand
noi avem Ia dispozitie atatea limbaje de programare traditionale in
care credem ca am putea transpune calculele dintr-o foaie de

0 introducere in Haskell 98 prin example - 222 - Dan Popa


calcul?
Raspunsul este ca foaia de calcul trebuie sa-$i gaseasca singura -
folosind algoritmi din teoria grafurilor - ordinea de realizare a
calculelor.
intr-o foaie de calcul poti scrie:
A1=B1+C1
81=2
C1=8
... $i in casuta A 1 va apare- calculat corect- rezultatul: 10.
Dar nici in C $i derivatele sale (C++, C#, Java etc) nici in clasicul
limbaj Pascal nu poti scrie o astfel de secventa de atribuiri, deoarece
toate aceste limbaje executa instructiunile de mai sus fn ordinea
data nu in ordinea fn care este nevoie de ele.
Dar in Haskell acest program este un program corect:
al=bl+cl
bl=2
c1=8
Cap9Par1 Ex1 .hs

Salvati-! sub un nume cum ar fi Cap9Par1 Ex1.hs si incercati-1.


radacina@computer:/home/dan/20 0x_I ntro-H98-Pf-CursS hugs Cap9Par1Exl.hs

II II II II II II II Hugs 98: Based on the Haskell 98 standard


II II II II II II II Copyright (c) 1994-2005
11 ---1 1 II wor ld Wide Web: http://haskell.org/hugs
II II Bugs: http://hackage.haskell.org/trac/hugs
II I I Version: September 2006 - - - - - - - - - - - - - - -

Haskell 98 mode: Restart with command l~ne option -98 to enable


extensions

0 introducere fn Haskell 98 prln example - 223 - Dan Popa


Type : ? Cor help
Main> al
10
Main> bl
2
Main> cl
8

Din exact acest motiv - faptul c~ Haskell poate calcula valorile in


ordinea in care este nevoie de ele - putem $i s~ transpunem toile de
calcul in Haskell - devenind programe dar putem $i s~ sc~p~m de
aparent incurcatul backpatching. S~ revenim Ia generatorul de cod I

9.2. Pregatlm monada stirilor '' formatul lnstructlunllor

Programul care urmeaz~ a ap~rut in intregime intr-o lucrare $tiitific~


a autorului acestei c~rti intitulat~: How to build a modular monadic
extensible compiler using The State Monad and pseudoconstructors
over monadic values, in revista Scientific Studies and Research.
Series Mathematics and Informatics vol. 21 no.2 2011 pag. 97-
116. A fost popularizat $i prin intermediul site-ului comunit~tii Haskell
unde poate fi g~sit Ia adresa:
http://www. haskell.orglhaskellwikj/Modu/ar Monadic Compilers for
Programming Languages. (2054 de vizitari pina Ia data cand scriu
aceste randuri mai mult de 1000 pe an.)

Codul incepe ca de obicei cu un antet de modul - l-am numit


MCOMP de Ia Modular Compiler intrucAt componente specifice
instructiunilor $i arborii sintactici pe care ii compileaz~ sunt ambele

0 introducere Tn Haskell 98 prln exemple - 224 - DanPopa


categorii - mocfulare. Practic puteti pastra din mini-limbajul compilat
mai jos doar cateva instructiuni, progarmul va tunctiona perfect. Sau
puteti scrie acela~i cod ca pe o colectie de module In Haskell care se
vor compila ~i lega lmpreuna - binelnteles lmpreuna cu monada
starilor. Este inclusa ~i biblioteca Data.Char- In ultima decada tunele
tunctii care existasera In biblioteca standard au tost mutate In acesta
colectie, DataChar. Este importata ~i biblioteca Monad deoarece
contine declaratiile clasei de tipuri Monad - iar noi dorim sa lucram
cu o monada, o instanta a acelei clase de tipuri.

module MCOMP where


import Monad
import Data.Char
-- v08. 30 iunie 2011

Monada starilor serve~te ca un tel de suport pentru programarea


imperativa avand o memorie cu valori asupra carora se tac ni~te

operatii care seamana cu ni$te atribuiri ~i au rolul acestora. Trebuie


totu$i sa precizam ce tel de valori manipuleaza starile. in cazul
generarii de cod ne intereseaza sa putem Iuera cu acei lntregi care
ne faceau problema Ia backpatching, adresele instructiunilor de salt.
le declaram de tipul lnt iar acest tip va fi tipul starilor din structura
noastra algebrica (monada).

-- Tipul. starilor , &ici adresele de inceput/sfa.rsit de cod


type S = Int

Monada starilor va servi - serve$te lntotdeauna - Ia a programa


calcule care - exact ca In programarea imperativa - schimba starea

0 introducere rn Haskell 98 prin exemple - 225 - Dan Popa


unei memorii $i produc un rezultat- s~ zicem de tipul a - apeland o
functie. 0 asemenea tranzitie de Ia S Ia o pereche de elemente cu
tipurile a $i S, perechea (a,S) , lncapsulat~ lntr-o cutie cu eticheta
SM va fi un element din monad~ care surprinde un calcul. il
declaram ca atare:

-- Monada starilor
data SM a = SM ( S -> (a , S))

Atunci cand declaram o instanta a clsei Monad trebuie s~ precizam


cum ajunge o valoare oarecare sa fie lncapsulat~ lntr-o astfel de
capsula $i cum se succed, cum se compun calculele. 0 actiune de
tip M a si o functie f:: a ->M b vor da ca rezultat o alt~ actiune, un M
b. Operatorul care urmeaza >>=$tim ca nu-l veti folosi direct, el se
afla Ia baza do-notatiei utilizate. Partea bun~ este c~ el are aceea$i
definitie de fiecare dat~ cand folosim monada st~rilor, deci li putem
scrie codul din declaratia de instanta pur $i simplu copiindu-1 din alt
program, In cazul clnd nul-am memorat.

instance Monad SM where


SM cl >>= fc2 = SM (\sO -> let (r , sl) = cl sO
SM c2 = fc2 r
in c 2 sl )
return k = SM ( \s -> (k , s))

Cat prive$te operatorul return, el este inteligibil: o valoare k se


transforma lntr-o capsula ce include o functie identica pentru
transferul starii - starea s nu se schimba - iar k este pus ca rezultat.

0 introducere in Haskell 98 prin exemple - 226 - Dan Popa


Exist~ bineinteles ~i situatia cand vrem s~ extragem valoarea
stocat~ in starea s. La aceasta II folosim pe readSM. Seam~n~

foarte mult cu return, dar aici rezultatul este chiar s-ui, extras din
stare. (intampl~tor aici starea e format~ chiar din acel s).

readSM : : SM S
readSM = SM (\a-> (s , a))

incercarea de a modifica valoarea stocata in starea s prin aplicarea


unei functii este numit~ ~i procesul de update al st~rii din structura
SM. Se define~te astfel:

updateSM :: (S -> S) -> SM S


updateSM f = SM (\a-> (a , fa))

Observati c~ astfel starea s este inlocuit~ cu o stare modificat~: f s.


Exist~ ~i actiunea care procedeaz~ ca Ia atribuiri. Vechea valoare se
pierde ~i stoc~m o noua valoare. Am numit aceast~ actiune writeSM.
Ne va fi foarte util~. N-ati uitat, cred, ca trecand Ia programarea
functionala, programatorii cunosc~tori ai limbajelor imperative simt
lipsa variabilelor globale in care sa poat~ stoca ceva ce pot reg~si

mai tarziu.

wri teSM :: S -> SM S


wri teSM a = SM (\s -> (a ,a))

La scrierea unui compilator, pe masur~ ce compilam ~i gener~m

codul, adresa Ia care lucram se incrementeaz~ (pentru a memora

0 introducers in Haskell 98 prin exemple - 227 - Dan Popa


aceastA adresA am avut noi nevoie de starea s I).~ ca pentru a
ne fi ceva mai comod Ia scrierea compilatorului se poate defini o
varianta a lui updateSM care In Icc sa aplice o functie arbitrara f
aplicA starii (adica adresei noastre memorate In stare) o
incrementare. Operatia e similara incrementarii contorului de locatii
de memoria din compilatoarele traditionale.

allocSM :: S -> SM S
allocSM 1 • SM (\a -> (a , s+l))

In acest moment suntem pregatiti sa dam startul unor calcule stocate


sub formA de capsula SM (din monada cu stare s). 0 stare initiala ~~
un proces de calcul ce va da un rezultatul de tip a actioneaza
lmpreuna pentru a produce rezultatul ~i o noua stare. A~a se
lanseaza in executie calculele din monada cu stari. De notat e ~~

faptul ca programand In do-notatie n-am facut decat sa pregatim o


actiune compusa, complexa, transformatoare de stare ~i

calculatoare de valori pe care abia acum o rulam. Definim deci:

runSM ; : s -> SM a -> (a , S) -- stare plus actiune d& o pereche


runSM aO (SM c) = c aO

Aceasta este functia cu ajutorul careia dam startul unor calcule dintr-
o capsula SM (monadica, a unei monade cu stari), indiferent ce
calcule sunt acolo prescrise. Ar putea fi - ca In exemplul care
urmeaza - lntregul set de operatii care se fac cu ocazia compilarii
arborelui sintactic incepand, sa zicem, de Ia adresa 0000. Acest
center al adresei instructiunii generate - initial 0000 - este pe post de

0 introducere rn Haskell 98 prin example - 228 - Dan Popa


stare initial~ pentru monada cu stari. (Dar puteti lncepe de Ia o alt~
adres~ dac~ doriti.) A$a !neat putem s~ scriem imediat eel mai scurt
compilator (de fapt generator de cod) din lume:

compile : : (Show t, Show tl) • > SM (t, [InstE tl]) -> IO ()


compile aEb • putStr . prettyprint $ EUnSM 0000 &Eb

Unde putStr este actiunea din Haskell care serve$te Ia afi$area


stringurilor In monada de 10 (de aici acel rezultat 10() - este al ei.) lar
prettyprint este functia care afi$eaz~ frumos Ia ie$ire codul generat.
In priviinta lui arb el este o structur~ din actiuni monadice care
reflect~ simultan $i sintaxa $i semantica - o alt~ inovatie. Aceast~
reprezentare monadic~ a arborelui sintactic (In care nu mai avem
frunze normale ci actiuni iar nodurile interioare sunt compuneri de
actiuni) am prezentat-o intr-o lucrare anterioar~. De altfel le veti
vedea mai departe, una cate una.
lnstructiunile care compun codul sunt colectate de compilator In lista
[ Instr tll . 0 cerint~ necesar~ este ca rezultatul compil~rii -care
include tipurile t $i t1 - s~ fie tip~ribil este ca lnse$i tipurile t $i t1 s~
fie tip~ribile , adic~ s~ fac~ parte din clasa de tipuri Show. De aici
acea preconditie (show t , show tl l •> ... care precede tipul
functiei compile. Aceste instructiuni din care va fi format codul
ma$in~ generat sunt declarate a$a:

- --- - INSTROCTI UNI ------------ ------------

data Instr a = Instr String a


deEiving (Show , Read , Eq)

0 introducers rn Haskell 98 prin exemple - 229 - Dan Popa


Examin~nd felul cum sunt declarate se vede ca sunt simple structuri
care contin un String - mnemonica instructiunii $i un argument de tip
oarecare a. (De obicei o adresa, un deplasament sau un operand -
fac precizarea de mai sus pentru cei care au studiat limbaje de
asamblare.)
Urmeaza o functie care ne afi$eaza codul rezultat intr-o maniera
familiara asambli$tilor. Declaratia deriving cshow , Read , Eq) ar fi facut
oricum tipul instructiunilor afi$abil dar felul cum arata ele tiparite de
functia implicit generata de sistemul Haskell, nu ne convine datorita
prezentei constructorului de date lnstr. De aici nevoia unei functii
destinata unei afi$ari elegante, iar o asemenea functie poarta
traditionalul nume de PRETTY PRINTER .

---- PRETTY PRINTER-ul -------------------

prettyprint ((a , l) ,b)


= "\n" ++ "Length of the code:" ++
show a ++ myprintl 0 1

mypri ntl nr []
• "\n" ++ show nr
myprintl nr ((lnstr a b) : 1)
• "\n " ++ show nr ++ "\t " ++
a ++ show b ++ myprintl (nr+l) 1

0 intrebare care se poate pune aici este de ce am folosit $i actiunea


putStr din monada de 10. Nu era de ajuns sa generam un String ?
lar raspunsul il gasiti deja in cod, trecut ca un comentariu:

-- Fara putstr nu apar efectele de la CR si TAB-uri

0 introducere in Haskell 98 prin exemple - 230 - Dan Pops


Noi am fi dorit ca prin compilarea cu compile a unui arbore cum este
eel de mai jos:

mainAO =compile (iif (qt (constant 10) (constant 20)) (attr 'x'
(constant 45)) (attr 'x • (const~t 50))

... sa rezulte un cod ma$ina care sa fie afi$at cum se vede in acest
comentariu:

( -- *MCOMP> mainAO

Lenqth of the code: 9


0 LD INT 10
1 LD INT 20
2 GTO
3 JZ 7
4 LD INT 45
5 STORE 120
6 JP9
7 LD INT 50
8 STORE 120
9*MCOMP>
--)

ori, traditional, codul in limbaj de asamblare se scrie pe randuri


succesive $i in coloane separate cu TAB-uri, cum vedeti mai sus.
A$a, de altfel, vor arata $i rezultatele functionarii pretty-printer-ului
nostru.

Urmeaza generatorul de cod modular, pe care l-am numit chiar


compilator, o denumire justificata de faptul ca va produce cod dar
mai putin justificata deoarece nu prime$te text sursa ci un arbore
sintactic format din actiuni. (Notati ca aici cuvantul modular nu

0 introducere in Haskell 98 prin exemple - 231 - Dan Popa


inseamna ca el este impar1it in module de program Haskell ci ca it
puteti fmparfi.)
Compilatorul care urmeaza produce doua rezultate:
- un numar care indica lungimea codului generat ~i care ne va servi
Ia calculul urmatoarei adrese ~i
- insa~i codul generat, sub forma de lista de instructiuni.
inceputul seriei de descrieri (in do-notatie) este marcat in sursa cu
cuvAntul (scriu pentru cei care examineaza codul publicat pe site.):
---------------------COMPILER--------------------------
9.3. Compilarea constantelor

La compilarea unei constante codul generat va contine o singura


instructiune, acel LD_INT care Ia executie va incarca fn vfJrful stivei
(N.B.) valoarea constantei. Numele ei, mnemonica, pot diferi de Ia
un limbaj de asamblare Ia altul dar ideea este aceea~i. in do notatie
sunt (pre)scrise actiunile de efectuat: intai se extrage din stare
adresa curenta Ia care s-a ajuns cu procesul de compilare, notata
aici aO, folosind actiunea readSM. Se calculeaza apoi a1 = aO + 1 -
adresa urmatoare ce va fi folosita in urmatoarea secventa de actiuni:
- o scriere a adresei calculate in memoria-stare a monadei (va fi
realizata de catre actiunea writeSM) ~i

producerea perechii lungime -lista de coduri cu return.

-- Compilarea constantei
-- return primeste o pereche cu doua argument.
-- lunqimea = 1 a codului si codul -•ina qenera t

0 introducere Fn Haskell 98 prin example - 232 - Dan Popa


constant nr
= do { aO <- readSM;
let al = aO +1
in do (
writeSM a1 ;
return (1, [ Inatr "LD_INT " nr) )

Este momentul sa vedem cum functioneaza. Fie scrieti un program


principal main = compile (constant 10) fie lansati de sub GHCI
actiunea mainA1 de mai jos:

-- Sub GBCI :
mainAl ., compile (constant 10)

Rularea actiunii mainA 1 duce Ia afi~area codului generat, in acest


caz este vorba de o singura instructiune LD_INT plasata Ia adresa
zero:
(--

*MCOMP> mainAl

Length of the code:l


0 LD INT 10
1*MCOMP>
--}

Se observa acum ~i de ce am avut nevoie de pretty-printer. Daca a~


fi cerut sistemului sa afi~eze rezultatul actiunii runSM (constant 10) a~

fi obtinut perechea de perechi de mai jos, din care aflam at~t adresa
finala c~t ~i lungimea codului. Notati ca numarul dinaintea codului
este chiar lungimea acestuia II

0 introducere in Haskell 98 prin exemp/e - 233 - DanPopa


-- Daca printaJD direct rezultatul actiunii: runSM (constant 10)
( -- 1*MCOJCP> runSM 0 (constant 10)
( (1 , [Ins tr "LD_INT " 10]) ,1)
*MCOJCP>
--}

9.4. Compllarea varlabllelor

Pentru a putea compila $i un fragment de program care contine


variabile este necesar~ o functie cares~ ne dea pozitia lor in tabela
de simboluri. in vederea experimentelor noastre folosim functia
provizorie symtable:

-- Tabela de s~luri dummy


symtable x • 0000 + ord x

Unde puteti inlocui 0000 cu adresa unde doriti s~ fie plasat


segmentul cu date.
Atentie: datorM felului cum am definit arborii, suma a dou~

constante se reprezint~ a$a (plus (constant 10) (constant 20) > $i


nu a$a (plus 10 20) . A$a c~ incercarea de a compila cea de-a doua
form~ v~ poate duce Ia aparitia unui mesaj de eroare criptic.

Ne ocup~m de compilarea variabilelor dar nu inainte de a nota faptul


c~ in st~nga atribuirilor vom folosi doar numele variabilelor nu
valoarea lor, ca urmare convenim ~ este corect s~ compil~m

atribuiri scrise a$a: (attr 1


s' (constant 10)) pentru a
deosebi valoarea variabilei, scris~ (variable 1
s ' ) , de numele ei

0 introducere in Haskell 98 prin example - 234 - DanPopa


scris doar al?a : I s I •

Modulul din compilator care va genera codul corespunzator utilizarii


valorii unei variabile va avea de executat urmatoarele actiuni:
- obtinerea adresei curente Ia care a ajuns compilarea, notata aO,
extrasa din stare, folosind readSM.
- calculul adresei urmatoare deoarece codul se lungel?te cu o
instructiune. (lgnoram faptul ca Ia anumite procesoare instructiunea
astfel generata ocupa mai mult spatiu, nu doar o locatie.)
- obtinerea adresei adr a variabilei noastre din tabela de simboluri
prin apelulla functia symtable.
- updare-ul starii folosind actiunea writeSM.
- generarea codului l?i a numarului de instructiuni din secventa. 1.

-- Compilarea variabilelor
variable s
= do { aO <- readSM;
let al "' aO +1
adr = aymtable (a)
in do {
writeSM al ;
return (1, (Inatr "LD_VAR " adr] )

lar daca dorim sa testam cum decurge compilarea variabilelor este


de ajuns sa definim l?i sa executam programul principal format din
urmatoarea actiune. (Daca doar acest cod: compile <variable ·a·>

va forma programul principal, else va numi firel?te doar main.)

0 introducere in Haskell 98 prin exemple - 235 - Dan Popa


mainBl = compile (variable 'a' )

Sau putem sa executam cu GHCI actiunea mainB1 de mai sus. Ca


rezultat vom obtine o linie de cod generat, plasata Ia adresa zero.
{--*MCOMP> mainBl

Length of the code : 1


0 LD VAR 97
--}

Un rezultat asemanator, tot o linie de cod generat, plasata Ia adresa


zero obtinem ~i daca compilam o variabila cu alt nume, aici
majuscula A. lnsa codul ASCII alliterei 'A'fiind 65, ceea ce obtinem
difera putin; observati I
mainB2 =compile (variable 'A')

( -- MCOMP> mainB2

Length of the code: 1


0 LD VAR 65

--}

9.5. Compilarea declaratiilor variabilelor

inainte de a descrie cum se compileaza declaratiile - in acest studiu


de caz - trebuie precizat ca limbajul didactic folosit pentru
demonstrarea tehnicilor de lucru Ia cursurile de limbaje ~i

compilatoare, limbajul Simple, este un limbaj monotip, adica un


limbaj care are un singur tip de variabile ~i nu are proceduri sau
functii cu variabile locale. Ca urmare doar programul principal, un

0 introducere in Haskell 98 prin exemple - 236 - Dan Popa


program care ar fi de forma :
let
integer a , b , s .
in
read a ;
read b ;
s : = a+b ;
write s;
end
Program in llmbajul Simple, cu variablle globale.

(prin existenta setului de variabile globale) produce alocarea de


spatiu pe stiv~ . Cat spatiu ? Atata cat num ~rul variabilelor de
compilat. lar codul generat contine o instructiune special~ DATA n
care are chiar acest efect Ia executie: deplaseaz~ indicatorul stivei in
sus cu n pozitii.

Deci actiunile pe care le va face compilatorul Ia i ntalnirea in arbore a


unui nod datas (in Haskell cuvantul data este rezervat) vor fi :
- aflarea adresei curente Ia care s-a ajuns cu compilarea, aO,
- calculul num~rului ultimei locatii - atentie, numerotarea poate s~
inceap~ de Ia zero, caz in care acest num~r e mai mic cu o unitate
decat n, num~rul de variabile $i l-am notat arg,
- calculul adresei a1 = aO +1 deoarece codul s-a lungit cu o locatie.
- stocarea adresei a 1 in stare
- generarea instructiunii de cod, in pereche cu num~rul. lat~:

0 introducere in Haskell 98 prin exemple - 237 - Dan Popa


-- Compilarea decla.r atiilor :
-- nr indica numarul de variabile din programul acria
-- intr-un limbaj monotip ai fa.r a proceduri/functii .
-- pt n variabile ae aloca locatiile 0,1, ... n-1
dataa n
= do ( aO <- readSM ;
let a1 = aO + 1
arg = n -1
in do (
writeSM al ;
return (1 , [I.natr "DATA " arg] )

Cum decurge compilarea se poate vedea din simpla lansare in


executie a actiunii urmatoare:
mainCl= compile (dataa 10)

(--

Length of the code : 1


0 DATA 9
l*MCOMP>

- -}

Limbajul Simple fiind un limbaj care nu are alte feluri de declaratii


(nici de proceduri, nici de functii nici de tipuri) putem trece Ia
compilarea instructiunilor, tratand cazurile unul dupa altul:

9.6. Compilarea instructiunii skip

Compilarea instructiunii skip poate fi tratata in doua moduri. Pe de o


parte o putem considera ca fiind o instructiune oarecare, ce

0 introducere in Haskell 98 prin example - 238 - Dan Popa


genereaz~ un cod de o anumM lungime (aici ins~ lungimea este
zero) ~i putem s~ ne imagin~m setul de actiuni ca fiind scris inc~t s~
cuprind~ etapele specifice. Asem~narea cu situatiile anterioare ar fi
in acest caz evident~. Un astfel de mod de compilare a lui skip ar fi
foarte asem~n~tor cu exemplele anterioare.
(--

-- Compilarea inatructiunii akip


akip :: SM (Int , [Inatr Inti)
a kip
= do ( aO <- readSM;
let al = aO + 0 -- pt a ae conform& aablonului
in do (
writeSM al ;
return (0, [I

--)

Dar se poate ~i mai simplu. Skip este elementul neutru Ia


concatenarea listelor de instructiuni generate prin compilare. Skip
urmat de o secvent~ de instructiuni d~ prin compilare exact codul
acelei secvente. Skip produce un cod format din zero instructiuni,
deci o list~ vid~ . De aici ideea de a-1 scrie altfel:
akip :: SM (Int,[Inatr Inti)
ak.i p • return (0, [I )
Cum s~ verificati aceasta ? Rulati actiunea compile skip, fie ca
program principal fie ca o simpl~ actiune lansat~ de Ia promptul
GHCI-ului.
IIIAinD1 • compile akip
Sau ati putea, ceva mai t~rziu, dar anticip~m . s~ compilati o

0 introducere fn Haskell 98 prin example - 239 - Dan Popa


I

instructiune compusa, de exemplu o alternativa avand pe ambele


ramuri cate un skip:

(--compile (iif (variable ' x') (skip) (skip)) --}

9.7. Compllarea lnstruqlunll read

in limbajul Simple read este o instructiune simpla - nu are ca


argument decat o variabila - ~i genereaza un cod format dintr-o
singura mnemonica IN_INT avand ca parametru adresa variabilei.
(
A$a era definita ma~ina virtuala a limbajului Simple, pe care o
[
(, folosea in cartea sa [Aab-05] profesorul Aaby. intrucat Haskell
(
I folose~te cuvantul read, el fiind un cuvant din biblioteca standard, noi
l am folosit mai jos readv. Actiunile de efectuat nu sunt multe, dat fiind
ca este vorba de generarea unui cod de o singura instructiune:
- extragem adresa aO pana Ia care a ajuns compilarea
- calculam a1 =ao + 1
- ex1ragem adresa variabilei din tabela de simboluri cu functia
symtable.
- stocam a 1 in stare folosind actiunea writeSM a monadei cu stari
- generam lista de instructiuni, de lungime 1

-- Compilarea operatiei de intrare read


readv s -- read eate utilizat deja in Baakel
= do ( aO <- readSM;
let a1 .. aO +1
adr = aymtable (a}
in do
writeSM a1;
retu.r n (1, [Inatr "IN_INT " adr]

0 introducere in Haskell 98 prin example - 240 - Dan Popa


Aceste dou~ actiuni servesc ca doua exemple, pentru a vedea
rezultatul compilarii a dou~ instructiuni de citire, dar pentru citirea a
dou~ variabile diferite:

mainD2 =compile (readv 'x ' )


mainD3 =compile (readv ' y ' )

RulAnd aceste dou~ actiuni In GHCI . .. rezultatul compilarii este in


cele doua cazuri cate-o instructiune IN_ INT dar care - Ia executie -
va stoca, valoarea citita Ia adresa potrivita :
(--

Lenqth of the code : 1


0 IN INT 120
1

*MCOMP> mainD3

Length of the code : 1


0 IN INT 121
1

*MCOMP>
--}

9.8. Compllarea lnstructlunli write

Aici avem de-a face cu compilarea unei instructiuni care produce un


cod mai lung. S~ vedem de ce. 0 instructiune write <eXp> este

0 introducere in Haskell 98 prin exemple - 241 - Dan Popa


lnsotitA de o expresie. Ca urmare, codul generat va contine Ia
lnceput codul expresiei - eel care Ia executie va calcula valoarea
expresiei - ~i abia apoi instructiunea de scriere care are doar rolul
de a recupera valoarea expresiei din locul unde a fost stocat~ - de
obicei acest loc este registrul acumulator al procesorului sau al ma~inii

virtuale (dar poate fi ~~ locatia din vArful stivei) ~~ a o tipari pe consol~ I


ecran. Este deci necesar~ o secvent~ mal lung~ de actiuni dintre care
prima va fi compilarea expresiei - ceea ce ne va aduce codul compilat al
acesteia ~i lungimea lui:
- intAi recuper~m din stare valoarea adresei a 1 pAn~ Ia care s-a ajuns cu
procesul de compilare
- apoi folosim actiunea exp (nu uitati c~ ace~i pseudoconstructori sunt ~~

actiuni monadice In acela~i timp II) ocazie cu care obtinem o pereche


format~ din codul cod1 ~~ lungimea sa 11 : (11 ,cod1l.
- apoi calcul~m adresa Ia care se va termina codul expresiei, ea se va
obtine prin adunarea dintre ao ~~ 11.
- urmeaz~ s~ tlnem cont de faptul c~ mal avem de ad~ugat o instructiune
- sto~m adresa a2 obtinut~ in starea monadei cu st~ri

- ~~ gener~m o pereche format~ din lungimea intregului cod produs prin


compilarea instructiunii write ~~ lungimea lui. Lungimea este prima din
pereche:
-- Compi1area acrieri1or aeamana cu cea a atribuiri1or
write exp
• do ( aO <-readSM;
(11 , codl) <- exp;
let al = aO + 11
a2 = a1 + 1
in do ( wri teSM a2;
return (11 + 1, concat [codl,

0 introducere In Haskell 98 prin example - 242 - Dan Popa


[Inatr "OOT_INT " 0] ] )

Pentru a proba generatorul de cod al instructiunii write este de ajuns


s~ execut~m actiunea:
mainE1 =compile (write (variable 'x'))

(--
IAnqth of the code: 2
0 LD VAR 120
1 OOT_INT 0
2
*MCOMP> mainE2
--)

Oat fiind c~ expresia a fost scurt~ se obtine un cod ma~in~ relativ


scurt, de doar doua instructiuni. Dar putem proba generatorul de cod
al instructiunii write ~i folosind un write care are ca argument o
expresie mai complicata (ceea ce presupune sa fi inclus In
programul compilatorului ~i instructiunile necesare pentru compilarea
sumelor de expresii- prezentat in subcapitolul urm~tor):

mainE2 =compile (write (plus (constant 10) (constant 20))


{--

*MCOMP> mainE2

IAnqth of the code: 4


0 LD INT 10
1 LD INT 20
2 ADD 0
3 OUT INT 0
4
-)

0 lntroducere in Haskell 98 prin example - 243 - Dan Popa


Observam ca s-a obtinut, s-a generat, un cod mai lung dar
deocamdata nu se vede imediat cum va functiona el. Ceea ce este
perfect explicabil doarece nu am descris In acest studiu de caz ~~

ma~ina virtuala a limbajului Simple. Dar ca sa va convingem de


corectitudinea acestui cod este de ajuns sa explicam faptul ca
fiecare dintre instructiunile LD_INT <valoare>, Ia executie, pune in
stiva valoarea respectiva iar fiecare operatie - cum este $i ADD
scoate din stiva cate valori are nevoie sa proceseze, le proceseaza
(in cazul lui ADD 0 le aduna) $i pune rezultatullnapoi in stiva. Acum
lucrurile sunt clare: primul LD_ INT 10 va pune pe 10 in stiva, al
doilea LD_INT 20 va stivui un 20 deasupra, se executa adunarea
ADD 0 care scoate pe 20 $i pe 10 din stiva - in aceasta ordine - le
ad una, 20+ 10=30 ~~ lasa acest 30 in varful stivei. La executia
instructiunii OUT_INT 0 acest 30 care este scos din stiva va fi afi~at.

A$a se executa acel write 10+20 compilat.

9.9. Compilarea instructiunli de atrlbulre

Compilarea instructiunilor de atribuire nu difera mult de compilarea


instructiunilor de scriere, a write-urilor. $i una $i cealata au cate o
expresie Ia dispozitie, vor produce intai un cod care calculeaza
valoarea expresiei $i apoi inca o instructiune. in cazul atribuirii este
vorba de o instructiune care- Ia executie- va stoca rezultatul extras
din varful stivei In locatia in care este stocata variabila. Binelnteles
ca pentru a afla adresa aceasta se consulta intai tabela de simboluri.

0 introducere in Haskell 98 prin exemple - 244 - DanPopa


-- Compilarea atribuirilor

attr • exp
= do ( aO <-readSM;
(l1 , codl) <- exp ;
let al = aO + 11
a2 = a1 + 1
adr .. aymtable •
in do ( writeSM a2 ;
return (11 + 1 , concat [codl ,
[Inatr " STORE " adr] ] )

Codul rezultat va fi o concatenare dintre codul rezultat prin


compilarea expresiei $i instructiunea Store <adres~ care are ca
parametru adresa variabilei din st~nga atribuirii. Lungimea lui este cu
1 mai mare dec~t lungimea codului expresiei iar adresa variabilei
numite so va calcula, (ova gasi) functia symtable.

9.1 0. Compilarea opera~iilor din expresii

Pentru situatia, de neevitat, In care expresia de compilat cuprinde


diferiti operatori aritmetici trebuie prevazute toate cazurile $i generat
codul lntr-o maniera corespunzatoare. Cum expresiile aritmetice
contin eel putin adunari scaderi lnmultiri $i lmpartiri vom include In
generatorul de cod $i linile acestea de program:

-- compilarea aumelor
plus expl exp2

0 introducere in Haskell 98 prin example - 245 - Dan Popa


= do { aO <-readSM;
(l1,cod1) <- exp1;
writeSM (a0+11);
(l2 , cod2) <- exp2;
let a3 = aO + 11 + 12 + 1
in do writeSM a3 ;
return (11+12+1 , concat [codl,
cod2 ,
[Instr "ADD " 0 1 1 )

-- Si similar, compi1area ce1or1a~te operatii

minus e .x p1 exp2
= do ( aO <-readSM;
(11,cod1) <- exp1;
writeSM (aO+ll);
(l2,cod2) <- exp2;
let a3 = aO + 11 + 12 + 1
in do writeSM a3 ;
return (11+12+1, concat [codl,
cod2 ,
[Instr "SUB " 0 1 1 )

mu~ t expl exp2


= do ( aO <-readSM;
(11,cod1) <- exp1;
wri teSM (&0+11) ;
(l2,cod2) <- exp2;
let a3 = aO + 11 + 12 + 1
in do writeSM a3 ;
return (11+12+1, concat [codl,
cod2,

0 introducere in Haskell 98 prin exemple - 246 - DanPopa


(Inau "MOLT " 0 J J )

divexp1 exp2
= do ( aO <-readSM;
(l1,codl) <- exp1;
writeSM (a0+l1) ;
(l2,cod2) <- exp2 ;
let a3 = aO + 11 + 12 + 1
in do ( wri teSM a3 ;
return (11+12+1, concat [cod1,
cod2,
[Inatr "OIV n 0 I l )

in toate cazurile de mai sus codul generat pentru o expresie cu un


operator binar $i doua subexpresii va fi format din secvente de cod
care pun In stiva valorile celor doua subexpresii Ia care se adauga o
instructiune corespunzatoare operatiei (aritmetice aici) dintre ele.
Aceasta, Ia executie va scoate din stiva cele doua valori calculate $i
va face operatia. Atentie Ia implementarea operatiilor necomutative,
deoarece acolo ordinea in care sunt puse valorile in stiva trebuie
urmarita cu atentie - valorile sunt intotdeauna scoase dintr-o stiva in
ordine inversa. De atentia aceasta vor trebui sa dea dovada
implementatorii ma$inii virtuale. Ni$te exemple de astfel de operatori
binari necomutativi gasiti in subcapitolul urmator.

0 introducere in Haske/198 prin exemple- 247- DanPopa


9.11. Compllarea comparatlllor din expresli

in limbaje care folosesc aceea$i conventie ca $i limbajul C - aceea


ca o comparatie va produce un unu (semnificand True) sau un zero
(semnificand False) iar celelate valori sunt considerate tot True,
operatiile de comparare a valorilor sunt de fapt ni$te operatii Ia fel cu
cele aritmetice. Bine, de acord, difera prin prioritatea lor, prin
ordinea operatiilor, dar de aceasta se ocupa analiza sintactica -in
momentul cand sose$te expresia de compilat, (sub forma de arbore
care se nume$te arbore operatoria~. problema prioritatii este deja
rezolvata. Programul generatorului de cod se completeaza acum cu:

eq expl exp2
• do ( aO <-readSM;
(11,codl) <- expl;
writeSM (aO+ll);
(12,cod2) <- exp2;
let a3 • aO + 11 + 12 + 1
in do ( writeSM a3;
return (11+12+1, concat [codl,
cod2,
[Inatr "EQ " 0 ] ] )

-- Si eate aimilar codul pentru compilarea celorlalte comparatii

1t expl exp2
= do ( aO <-readSM;
(1l,codl) <- expl ;
writeSM (&0+11);

0 introducere fn Haskell 98 prin example - 248 - Dan Popa

t1
(12,cod2) <- exp2;
let a3 = aO + 11 + 12 + 1
in do ( writeSM a3 ;
return (11+12+1 , concat [cod1 ,
cod2 ,
[Ina tr "LT " 0 1 1 )

qt expl exp2
= do ( aO <-readSM;
(ll , codl) <- expl ;
writeSM (aO+ll);
(12,cod2) <- exp2;
let a3 = aO + 11 + 12 + 1
in do ( wri teSM a3 ;
return (11+12+1 , concat [cod1 ,
cod2 ,
[Inatr "GT " 0 1 1 )

Am inclus doar trei exemple, dar este suficient. Mnemonicile folosite


vin de Ia EQ - equal din engleza inseamna egal <=> , LT de Ia less
than inseamna mai mic dec~t (<) iar GT, greater than inseamna mai
mare ca (>).
Limbajul Simple nu are neaparat nevoie de un NEQ el este inlocuibil
cu un NOT $i un EQ. Sau puteti opta sa completati generatorul de
cod cu toate operatiile, eventual in speranta ca-l veti refolosi.

0 introducere in Haskell 98 prin exemple - 249 - Dan Popa


9.12. Compllarea lnstruqiunil conditionale (if)

Limbajele de programare nu se reduc numai Ia expresii, atribuiri ~i

operatii de intrare ~i ie~ire a datelor simple. Daca ar fi a~a. primul


program care face banala lmpartire a lui x Ia y ar da o eroare cand
lncercam sa-i dam printre datele de intrare pe y avand valoarea
zero. Ati lnvatat Ia lectiile de programare structurata ca toate
limbajele au o instructiune conditionala sau (ca In Haskell ~i In C) o
expresie conditionala. Daca este o instructiune se nume~te ~i

instructiune alternativa - din cauza felului altemativ In care se


executa (sub)instructiunile de pe ramurile ei .
Codul generat de instructiunea if este destul de complicat. incepe cu
codul generat de conditie. Urmeaza un JZ -jump zero Ia o adresa
unde este plasat lnceputul ramurii else, se continua cu codul generat
de ramura then iar acesta este urmat de un GOTO Ia adresa de
dupa instructiunea if. Abia acum urmeaza codul ramurii else.

-- Cod qenerat de instructiunea if,


mainll • compile (iif (qt (constant 10) (constant 20)) (attr 'x'
(constant 45)) (attr 'x' (constant 50))

{--
Lenqth of the code: 9
0 LD INT 10 constant& 10 va fi pus& in stiva
1 LD INT 20 constant& 20 va fi pusa in stiva
2 GT 0 comparatia dintre ele
3 JZ 7 daca da zero (fals) , se sare la ramura else
4 LD INT 45 incepe ramura then cu constant& 45 in stiva
s STORE 120 care e stocata la adresa 120
6 JP9 si teDDinam executia sarind dupa if

0 introducers in Haskell 98 prin exemple - 250 - Dan Popa


1 LD_INT SO incepe ramura elae cu constant& SO in ativa
8 STORE 120 stocam pe SO la adreaa 120 a vari&bi lei x
9 aici ar incepe urmatoarea instructiune
*MCOMP>

--)

Observati ca r~ndurile 0,1,2 de mai sus contin codul produs prin


compilarea conditiei structurii alternative; (gt (constant 10)

(constant 20) > ; r~ndurile 4,5 ~i respectiv 7,8 sunt cele provenite din
compilarea atribuirilor: (attr ' x ' (constant 4S) > respectiv (attr •x•

(constant so)> iar restul sunt instructiuni (In limbaj de asamblare)


care ghideaza fluxul executiei In interiorul structurii alternative: saltul
Ia codul echivalent ramurii else din r~ndul al 3-lea ~i saltul de dupa
executia codului ramurii then Ia final - r~ndul al 6-lea. Fire~te,

niciodata if-ul, alternativa, nu executa ~i ce are pe ramura then ~i ce


are pe ramura else.
Oat fiind ca alternativa este o adevarata instructiune structurata,
compusa, ce contine In interiorul sau alte doua instructtiuni compuse
(cele de pe ramurile then ~i else) l mpreuna cu expresia - conditie,
compilarea acestei structuri se va folosi de compilarea tuturor celor
trei substructuri ale sale. (Acest mod de a aborda semnificatia unei
structuri compuse este numit uneori ~i "principiul compozitional al
semanticiilor.") Din acest motiv veti gasi In descrierea compilarii
alternativei cele trei r~nduri:

(11 , cod1) <- cond;


(l2,cod2) <- sl ;
(l3,cod3) <- s2 ;

0 introducere in Haske/198 prin example- 251 - Dan Popa


Rolul lor este sa declan$eze actiunile de generare de cod pentru
expresie :;;i substructuri, sa obtina secventele de cod produse
cod1, cod.2, cod3 $i sa le memoreze lmpreuna cu lungimile acestora
11,12,13 .

iif cond a1 a2
= do ( aO <-readSM ;
(11 , cod1) <- cond ;
writeSM (a0+11+1) ;
(12,cod.2) <- s1 ;
writeSM (aO + 11 + 1 + 12 + 1)
(13,cod3) <- s2 ;
writeSM (aO + 11 + 1 + 12 + 1 +13) ;
r•turn (11+1+12+1+13, concat
[codl,
[Instr "JZ " (a0+11+1+12+1) ],
cod.2 ,
[Instr "JP " (&0+11+1+12+1+13)] ,
cod3 ] )

Nota: Explicam Ia lnceputul capitolului ca backpatching-ul s-ar putea


lnlocui cu mecanismele de lazy-evaluation. Este corect ca principiu
dar In programul de mai sus am folosit o tehnica mai apropiata de
stilul obi$nuit de programare structurata: Am generat separat cele
trei secvente de cod :;;i cele trei lungimi ale lor iar din aceste date am
alcatuit at~t codul final c~t :;;i calculul adreselor de salt. ~tiind

lungimile codurilor substructurilor toata treaba se reducea Ia simple


adunari lntre adrese $i lungimi. Totu:;;i lazy-evaluation-ul intervine
undeva: Ia faptul ca atunci c~nd compileaza substructuri care Inca

0 introducere in Haskell 98 prin example - 252 - Dan Popa


nu :?tie unde-s plasate in memoria, compilatorul folose$e adresa
luatA din stare. Din acest motiv a trebuit sa upgradez starea dupa
fiecare compilare de substructura. incercati codul tara acele update-
uri de stare (facute cu actiunea writeSM) $i examinati rezultatele.

-- Alt cod qenerat de o i natructiune alternati va,


m&ini2 = compile (iif (va.riable 'x 1 ) (attr 'x' (constant 1))
(attr 1
x 1
(constant 2 ) ) )

(- -

•MCOMP> m&i nl2

Lenqth of the code: 7


0 LD VAR 120
1 JZ S
2 LD INT 1
3 STORE 12 0
4 JP7
s LD INT 2
6 STORE 120
7

--}

Exercitiu: Comentati rand cu rand codul generat de exemplul de mai


sus, dupa modelul comentariilor de Ia exemplul anterior. Observati
cum se fac salturile.

0 introducere rn Haskell 98 prin exemple - 253 - Dan Popa

~ - -
9.13. Compllarea instructiunii structurale: secven~

Versiunea de limbaj Simple din cartea profesorului A.Aaby permitea


secvente formate din oricAte instructiuni. Din punct de vedere
semantic este insa suficient sa ~tim sa generam cod pentru
instructiunea compusa care este succesiunea a doua instructiuni. De
ce ? Daca ar fi mai multe, de exemplu trei am spune ca sunt doar
doua din care a doua este tot o succesiune de alta doua, ~.a.m.d.

Este adevarat ca in cazul acestei abordari ii va reveni parserului


(componenta soft care transforma textul in arbore) sarcina de a
construi arborele pentru o secventa de eel putin trei instructiuni:

read a ;
read b ;
x: ::: 2 ;

lar arborele va fi de forma:


(sequ . ... ( sequ .........))
Mai exact, textul sursa de mai sus ar produce arborele :
(aequ (readv ' a') (aequ (readv 'b') (attr ' x ' (constant 2 )) ) )

iar generatorului de cod, ca sa poata compila ~i astfel de secvente


lungi, i-ar fi suficient sa poata genera cod pentru secvente de doua
instructiuni compuse.

Compilarea secventei de instructiuni compuse decurge a~a cum ne


a$teptam, inUii se compileaza prima instructiune compusa din

0 introducere in Haskell 98 prin exemple - 254 - Dan Popa


secvent~ ... obtinftndu-se ... lungimea ei 11 $i codul ei cod1, se
consemneaz~ In stare c~ s-a ajuns cu generarea de cod Ia o alt~

adres~ ($i deci adresa liber~ stocat~ In stare se modific~). Apoi se


lanseaz~ o nou~ serie de actiuni cuprinzftnd generarea de cod
pentru a doua instructiune. Rezulta un 12 $i un cod2. Codul rezultat
din lntreaga secvent~ de dou~ instructiuni va fi concatenarea
(listelor) calor dou~ coduri cu functia concat (sau operatorul ++ dac~

doriti) $iva avea, binelnteles lungimea 11 +12.


-- Compilarea secventelor
-- Adresa intermediara de dupa codl trebuie salvata i n stare

sequ s1 s2 - -cuvantul seq e rezervat in Haskell


= do { aO <-readSM;
(11 , codl) < - s1 ;
let a2 = aO +11
in do
wri teSM a2 ;
adresa de inceput a celei de - a doua secvente
-- de cod trebuie memorata in stare
-- altfel se genereaza adrese i ncorecte
a2 < - readSM; -- arata puti.n redundant aici
(12 , cod2) <- s2 ;
let a4 = a2 + 12
in do wri teSM a4 ;
return (11+12 , concat [cod1, cod2] )

Este momentul s~ vedem cum se compileaz~ o secvent~ . aici este


dat~ ca exemplu una format~ din dou~ atribuiri:
mainW = compile (sequ (attr 'x ' (constant 45)) (attr 'y '

0 introducere Fn Haskell 98 prin exemple - 255 - Dan Popa


(constant 50))

(--*MCOMP> mainW

Lenqth of the code: 4


0 LD INT 45 ; preqatim valoarea 45 punand-o pe ativa
1 STORE 120 o extraqem si o incarcam la adreaa 120
2 LD INT 50 preqat.im valoarea 50 punand-o pe ativa
3 STORE 121 o extraqem si o incarcam 1a adreaa 121
4*MCOMP>
--}

9.14. Compilarea unellnstructluni ciclice: bucla while

Compilarea buclei while, mai exact generarea codului ei, ne puna in


fata unor problema care seam~n~ cu cea intalnit~ Ia instructiunea
alternativ~. Pe de o parte este o structur~ compus~ din expresia-
conditie ~i blocul de instructiuni de repetat iar codul generat, da~ in
urma testului rezuM c~ este conditia neindeplinit~. va face un salt
(conditionat; nota bene) dup~ bucla while; Ia codul generat de
urmatoarea instructiune din programul surs~. Cea de dup~ while.
Pentru a fi mai u~or de urm~rit vom incepe ~i aici cu un cod generat,
comentat de noi. Acest cod nu este unul pragmatic - probabil nici un
programator nu ar scrie o asemenea bucl~ ;) - dar ne-a interesat s~

explic~m doar procesul de generare a codului ~i structura acestuia,


cu salturile din ea; (unul plasat dup~ conditie iar altulla final, necesar
fiind pentru reluarea buclei). Fire~te, puteti inlocui conditia ~i corpul
buclei dat de noi ca exemplu cu ceva mai complex, inspirat din
realitate. Acesta e doar un exemplu minimal format dintr-o singur~

0 introducere in Haskell 98 prin exemple - 256 - Dan Popa


conditie $1 o singur~ instructiune:
-- Cod qenerat de o instructiune while apart•
mainWO • compile (while (qt (constant 10) (constant 20)) (attr
'x ' (constant 45)))
{--

Lenqth of the code : 7


0 LD INT 10 va puna in stiva valoarea constantei 10
1 LD INT 20 va puna in stiva valoarea constantei 20
2 GT 0 va scoate valorile din stiva si le va compara
3 JZ 7 daca e conditia falsa (zero) sara la adresa 7
4 LD INT 45 corpul buclei incepe aici
5 STORE 120 si va executa o atribuire
6 JPO iar bucla sa reia cu un salt la inceput
*MCOMP> - -}

Partea din generatorul de cod care genereaz~ codul buclei while


(inclusiv secventa de cod pe care ati citit-o mai lnainte tot cu el a fast
generat~) este transcris~ in continuare. Actiunile pe care le are de
realizat compilatorul sunt:
- aflarea adresei ao de unde 'fncepe generarea de cod
- generarea codului conditiei- care e prima substructur~ a lui while ;
ocazie cu care se memoreaz~ $i lungimea 11 a codului cod1.
- stocarea in stare a adresei Ia care va incepe codul corpului buclei
- generarea codului corpului buclei, (inclusiv lungimea lui 12)
- calculul adresei a2, prima ad res~ de dup~ bucl~

- stocarea ei In stare - aceast~ ad res~ va folosi Ia generarea codului


urmatoarei instruc(iuni de dupa while-ul In discutie I
- construirea structurii de date (nu uitati c~ este o pereche) ce
contine lungimea (11+12+2) codului generat impreun~ cu codul
produs prin concatenarea codurilor (concatenare cu conca~ :

0 introducere in Haskell 98 prin exemple - 257 - Dan Popa


-- Compilarea buclei While
while cond s1
= do ( aO <-readSM;
(11,codl) <- cond;
writeSM (aO+ll+l) ; observati acel +1 care laaa un loc
(12 , cod2) <- s1 ; compilarea a2 incepe de aici
let a2 • aO + 11 + 1 + 12+1
in do writeSM a2 ;
return (11+12+2 , concat [codl ,
[Inatr "JZ " a2 1 ,
cod2 ,
[Inatr "JP " aO] ] )

Sa compilam ~i o structura while ceva mai pragmatica, mai


asemanatoare cu buclele care chiar ar putea exista in programele
scrise de programatori.

-- Iata alt cod generat de o inatruetiune while


mainWl • compile (while (qt (variable 'x') (constant 0)) (attr
' x ' (minus (variable ' x ' ) (constant 1)) ))
{--
Length of the code : 9
0 LD VAR 120
1 LD INT 0
2 GT 0
3 JZ9
4 LD_VAR 120
5 LD INT 1
6 SUB 0
7 STORE 120
8 JPO
9

0 introducere in Haskell 98 prin example - 258 - Dan Popa


*MCOMP>
--)

-i.nE3 = compile (while (qt (constant 10) (constant 20)) (attr


'x ' (conatant 45 ) ) )

{--
Length of the code : 7
0 LD INT 10
1 LD INT 20
2 GT 0
3 JZ 7
4 LD INT 45
5 STORE 120
6 JPO
*MCOMP>
--)

Exercitlu: comentati codurile de mai sus care au fost generate


pornind de Ia (arborii unor astfel) de instructiuni while.

9.15. Compilarea unei instructiunii cicllce: bucla do-while

Limbajele de programare moderne contin $i o bucla while cu test


final. 0 puteti gasi Tn unele limbaje sub numele do ... while ... , bucla
cu test final. in altele, cum este limbajul Pascal $i limbajele derivate
din acesta, bucla cu test final este numita repeat ... until...(este
adevarat, este diferita, are conditia de oprire inversa, negata). De
generarea codurilor unor astfel de instructiuni repetitive ne ocupam
Tn continuare. Bucla do ... while ... fiind o bucla cu testul final care
se opre$te c~nd conditia de test este fa/sa, codul generat ar arata ca

0 introducere Fn Haskell 98 prin exemple - 259 - Dan Popa


in acest exemplu minimal (~i tot nepragmatic ;) ).
-- Cod generat de o instructiune do-while
mai.nDW1 = compile (dowhi1e (attr 'x ' (constant 45)) (gt
(constant 10) (constant 20) ) )
(--

*MCOMP> mainDW1

Length of the code : 6


0 LD_INT 45 va puna constanta 45 in stiva
1 STORE 120 o va scoate si o va plasa la adresa 120
2 LD INT 10 incepe testarea conditiei, 10 e pus in stiva
3 LD INT 20 •• va pune in stiva si 20, celelalt element
4 GT 0 iar GT va face compararea lor , apoi daca ...
5 JNZ 0 ... este indeplini ta condi tia sa reia bucla.
6*MCOMP>

--}

Aici se pot face unele comentarii, mai detaliate. Limbajele de


programare implementeaza bucle cu test final dar conditia de ie~ire

din bucla difera de Ia un limbaj Ia altul. Bucla repeat . . . until... din


Pascal, de care pomeneam se repeta pana cand condifia devine
adevarata pe cand do-while se repeta aUit timp cat conditia este
adevarata. Depinzand de situatie - examinati cu atentie in ce situatie
va aflati - saltul final spre inceputul buclei poate fi un JZ <adresa>
sau JNZ <adresa>. (Un "jump-zero" sau un "jump-non-zero"!).

Codul generat este insa mai simplu decat eel produs de o


instructiune alternativa, deoarece bucla are doar o sub-instructiune
(partea care se repeta) ~i o conditie (conditia din final) care sunt
compilate impreuna. Ele vor fi compilate in aceasta ordine iar Ia final
se adauga saltul conditionat necesar pentru reluarea buclei. El va fi

0 introducere Fn Haskell 98 prin exemple - 260 - Dan Papa


executat In cazul cAnd valoarea conditiei o cere.

--Inatructiunea ae aerie cu un ~do H dar nu-l foloaiti ainqur


--In Haskell, un do ainqur a-ar confunda cu do-notatia
--Am acria deci : dowhile <inatructiunea> <conditia>
dowhile a1 cond
= do ( aO <-readSN;
(11,codl) <- al;
writeSM (aO+ll) ;
(12,cod2) <- cond;
let a2 ~ aO + 11 + 12 + 1
in do ( wri teSM a2;
return (11+12+1, concat [codl,
cod2,
[Inatr "JNZ " aO ] ] )

-- Alt exemplu de cod qenerat de un do ... while ... ,mai praqmatic


mainDW2 = c0111pile (dowbile (a ttr ' x' (a.inua (variable 'x')
(constant 1)) ) (qt (variable ' x') (constant 0)) )
(--

Lenqth of the code : 8


0 LD VAR 120 ; va pune in ativa valoarea variabilei 'x'
1 LD INT 1 ; va pune in ativa intrequl 1
2 SUB 0 ; va efectua acaderea
3 STORE 120 ; va atoca in locatia variabilei 'x' rezu~tatul

4 LD VAR 120 ; pe care il va plaaa in ativa apre comparare


5 LD INT 0 ; celalal t t~u:men al compara tiei • 0, pe ativa
6 GT 0 ; aunt comparati, daca ae obt.i ne 1-True . ..
7 JNZ 0 ; el eat. diferit de zero ai bucla •• reia
8 ; altfel ae ajunqe aiel , la urmatoarea
; inatructiune

0 introducere in Haskell 98 prin example - 261 - DanPopa

0
ji, Ill ,I/
*MCOMP>
--}

9.16. Compilarea programului principal

Deoarece Ia generarea de cod atat declaratiile cat $i instructiunile


genereaza cod - deci sunt tratate Ia tel - generarea de cod pentru
un program principal format din declaratii $i instructiuni decurge
practic Ia tel ca $i generarea de cod pentru o secventa de doua
instructiuni. (Aceasta insa presupunand ca dintr-o faza anterioara
am aflat deja numarul de variabile din program $i l-am trecut in nodul
corespunzator din arbore.)
-- In ip . de mai sus compilarea proqramului • ca aceea a secventei
-- deci eel mai simplu e sao definim ca fiind totuna cu 1
Sequ 1 •

proqram sl s2 s sequ sl s2

~i iata-ne aproape de finalul demersului nostru: sa examinam acum


codul generat de un program simplu, care declara doua variabile $i
executa doua atribuiri asupra lor:
~nX = compile
(proqram (datas 2}
(aequ (attr 1
x 1 (constant 45))
(att.r 1
Y' (constant 50))

{--
*MCOMP> mainX
IAnqth of the code : 5

0 introducere in Haskell 98 prin example - 262 - Dan Popa


0 DATA 1 dec1aratia pentru doua variabile 10, 11
1 LD INT 45 incarca in ativa conatanb 45
2 STORE 120 apoi o va incarca la adreaa lui 'x'
3 LD INT SO incarca in stiva va1oarea 50
4 STORE 121 apoi o va incarca la adreaa lui •y•
S*MCOMP>
--)

Ca un exemplu alternativ, vom urmari generarea de cod de catre un


program care contine $i opera~i de intrare-ie$ire: o citire $i o scriere.

-- Compilam un program cu instructiuni de I/0:


m.ainO = compile
(program (dataa 2)
(sequ (readv • x •)
(write (variable 'x' )

lata $i codul generat Ia compilarea programului care contine


operatiile de intrare-ie$ire de mai inainte:
(--

*MCOMP> mainO

Length of the code: 4


0 DATA 1 declaratia vari&bilelor
1 IN INT 120 se citeste data;se va stoca in locatia lui'x'
2 LD VAR 120 se incarca pe ativa valoarea din locatia 'x'
3 OOT INT 0 o va lua de acolo de OOT_INT apr• afisare
4*MCOMP>
--)

Probabil intuiti deja ca primele testari care se fac unui compilator


(a$a cum le facem $i noi generatorului nostru de cod) se fac cu
programe structurate din ce in ce mai complicate, eel putin p~na se
ajunge Ia unul care contine toate felurile de instructiuni - $i toate

0 introducere in Haskell 98 prin example - 263 - Dan Popa


felurile de expresii ori de operatori. Verificarea matematica riguroasa
se face prin inductie structurala dupa complexitatea programului
(numarul maxim de structuri imbricate). Urmatorul test ar putea fide
exemplu sa avem de compilat programul:
main = compile
(proqram (dat&s 2)

(sequ (readv 'n 1 )


(sequ (iif (qt (variable 'n 1 ) (constant 10)
(attr 1
x 1
(constant 1))
(skip)

(skip)
))

lata !?i codul generat Ia compilarea programului de mai inainte:


(--
Lenqth of the code : 9
0 DATA 1
1 IN INT 110
2 LD VAR 110
3 LD INT 10
4 GTO
5 JZ g
6 LD INT 1
7 STORE 120
8 JP9
9*MCOMP>

--)

Unii critici ar putea repro$a faptul ca nu am prevazut !?i felul cum s-ar
genera cod pentru o instructiune altemativa cu o singura ramura. Nu

0 introducere in Haskell 98 prin example - 264 - DanPopa


este o problema pentru generatorul nostru, putem pur $i simplu
considera ca pe cealatta ramura din instructiunea atternativa a fost
un skip. Deci e ca $i cum ar genera eodul unui program sau unui
fragment de program similar eu eel indieat (prin arborele de) mai jos:

main1 • compile
(iif (qt (variable 'n') (constant 10)
(attr 'x' (constant 1))
(skip)

(--

Dat fiind ea instructiunea skip nu genereazaa cod de loe (revedeti


subcapitolul euprinzand paragrafele despre codul generat Ia
eompilarea unui skip), eodul obtinut este praetie eel care ar fi fost
produs de o instruetiune alternativa eu o singura ramura (if ... then ... ).
7*MCOMP> mainl

Lenqth of the code:7


0 LD VAR 110
1 LD INT 10
2 GT 0
3 JZ 7
4 LD INT 1
5 STORE 120

6 JP 7
7*MCOMP>
--)

-- Ok
main2 = compile
(sequ (iif (qt (variable ' n') (constant 10)
(attr • x • (constant 1))
(skip)

0 introducere in Haskell 98 prin exemple - 265 - Dan Popa


(skip)

{--
IAnqth of the code: 7
0 LD VAR 110
1 LD INT 10
2 GT 0
3 JZ 7
4 LDINTl
5 STORE 120
6 JP 7
7*MCOMP>

--}

mai.n 3 = compile
(sequ (readv 'x')
(sequ (iif (qt (variable ' n') (constant 10) )
(attr 'x' (constant 1))
(skip)

(skip)
))
(--

Length of the code: 8


0 IN INT 120
1 LD VAR 110
2 LD INT 10
3 GT 0
4 JZ 8
5 LD INT 1
6 STORE 120
7 JP 8
8*MCOMP>

0 introducere in Haskell 98 prin exemple - 266 - Dan Popa


--}

main3b = compile
(sequ (constant 111)
(sequ (iif (qt (variable 'n') (constant 10)
(attr 'x' (constant 1))
(skip)

(skip)
Il
{- -
8*MCOMP> main3b

Length of the code : 8


0 LD INT 111
1 LD VAR 110
2 LD INT 10
3 GT 0
4 JZ 8
5 LD INT 1
6 STORE 120
7 JP8
8*MCOMP>
--}

9. 17. Compilarea unul program principal: exemplul profesorului


Anthony A. Aaby

Programul al carui reprezentare arborescenta o compilam in


exemplul care urmeaza provine din cartea Compiler Construction
using Flex and Bison, fig 7.1 de Ia pg. 75 din editia 2006, editura

0 introducere in Haskell 98 prin exemple - 267 - Dan Popa


Edusoft, Ba~u .

. .i n4 • putStr . prettypri nt $ runSM 0


(proqram (datu 2)
<••qu (readv •n • )
(•equ (iif (lt (vari able 'n' ) (con•tant 10) )
(attr ' x ' (con•tant 1))

(•kip)

(while (lt (variable • n •) (con•tant 10))


(•equ (attr 'x' (mult (con•tant 5)
(variable 'x' ) )

(attr 'n' (plu• (variable 'n')


(con•tant 1))

(--

Length of the code : 22


0 DATA 1
1 IN INT 110
2 LD VAR 110
3 LD INT 10
4 LT 0
5 JZ g
6 LD INT 1
7 STORE 120
8 JP9

0 introducers fn Haskell 98 prin example - 268 - Dan Popa


9 LD VAR 110
10 LD INT 10
11 LT 0
12 JZ 22
13 LD INT 5
14 LD VAR 120
15 MtJLT 0
16 STORE 120
17 LD VAR 110
18 LD INT 1
19 ADO 0
20 STORE 110
21 JP9
22*MCOMP>

--}

Am compilat tocmai acest program cu intentia de a verifica daca


generatorul nostru de cod produce un cod similar cu eel prezentat in
volumul amintit. Comparatia lor, cititorul o poate face cu U$Urinta,
volumul profesorului Anthony A Aaby se gase$te $1 pe Internet fiind
oferit ba de un site ba de altul - este o carte sub licenta Iibera- iar
comparatia nu lasa nici o urma de indoiala asupra reu$itei proiectului
generatorului nostru de cod. Pe care va invitam sa-l folositi in alte
proiecte.

0 introducere fn Haskell 98 prin example - 269 - Dan Popa


Anexa A : lnstalarea Hugs98 pe o ma$ina Windows

Descarcati fi$ierul cu arhiva Hugs98 pentru Windows de pe Internet.


El va fi afi~t pe ecran sub forma urmatoarei iconite:

Cu un dublu click pe iconita de mai sus porniti acest program, care


este o arhiva autoextractiva ce va afi$a urmatoarea fereastra:

E•..c to f"cldor
~ lc\WINDO\IIS\TEMP\
[-*'Gf"ololtl
r tom-'*- - - . g
lr,...... I
r. Q_lcbl, etkl ·!""-£., "" I
r ~(clan,.,...,.,
~ ~ I

Puteti schimba directorul cu unul ales de dumneavoastra pentru a


colectiona in el fi$ierele separate care formeaza kit-ul de instalare.
Am creat un director Hugs98. Fi$ierele extrase vor arata a$a:

C4or
~
- -
~ :J1
'"*
)<

~
..,.,ce
.!)
""'"" -~
~
... ..l!l.. tlJ.....
~
~
~-

~ ~
...... 21
......
'-""

0 introducers Tn Haskell 98 prin exemple - 270 - Dan Popa


Programul de instalare poart~ numele clasic, Setup.exe. Dac~ n-a
pornit automat, il puteti pomi cu un dublu click ori folosind
combinatia click plus ENTER. Dup~ ce pome~te va afi~a mesajul de
prezentare a proprietarilor copyright-ului.
\nl1w.u E' I trPnsP Aw PPIIWIII [)

htHugo 98.,...,.a~(c1Morrk PJonet. ....._ R..t.lhe


.H..W~.andlhe~Gr....._.,_..,San:eand
Tec:Makg. 199f..1999.AI..,.. ..-..d. and a~ • flee

..-
............. lclooorG .....
~~-and- ........ andbnlly __ .................
lllllllicoiDI. .. pomobd ~ ....... ~ c:oncillona

· R~ olocuco CCido-r«., lhe._CGIJl'l9'i ,.,.,


. . bl .. c:oncillona and ... ~ dodoiMr
·R............ inbnllylarm-~lheoblweCGIJl'l9'i
,.,. ... bl .. c:oncillona and lheloloooing dec~.. .....

OoJC~U...:>~~~~.,Ihe-olhll**lrvU...Apeomon? •IIOUtt-No s~
. .. - TonloiiHugo98.JCIU-«CCIIIIIw~

Mesajul este ceva mai lung, folositi barele de defilare din dreapta
ferestrei pentru a-1 derula. (E suficient s~ dati click pe butonul cu
s~geata orientat~ Tn jos pentru a accepta conditiile ~i a continua.}

....-1 _ _ -t.PrloGEOOWN.._,Io_

___
._-.g~.-_..

1!liil .. _., .. ~

..__
-....v
-

· N- -
.. --"'<Mdocl.-.... _
....... a l . . . . . - . - t . - . g -....

.......,.,._..,
alhrCOIJIO!IIhtholltoronorlhe-alitt
.....-.....,beuoedlo-..co_pr _ _ ,_

THIS SOFTWARE IS PfiOVI0£0 8Y lH£ COPt'RIGHT HOI.O£RSANO TH(


CONTRI8UTOAS
')ISIS" /1>110 NN E)q>RE$S OR IMPU£0 WARRANTIES. D«::.UOING. 8UT NOT
UMIT£0 TO. THE IMPU[OWARRAN TIES OF MEROWil A81UTY NIO fiTNESS
FOR
P.toiiTICUlAR PURPOSE All[ OlSCUIMO IN NO E\IENT SHALL THl
T

0o)'OU- .. h-clh~~~7 M)'OU- No. $ -


.. _ ro-..H\9198 )'OU...,._.__

<llad. I X• Ho

Sara de defilare va ar~ta c~t ati parcurs din text iar Tn final se va opri

0 introducere in Haskell 98 prin exemple - 271 - Dan Popa


In cea mai de jos pozitie posibila.
Softwat P LtrPUSP Al/.li'PIIII'nt E3

PARTICULAR PURPOSE ARE DISQ.AIMED IN NO EVENT SHALL THE


COPVRIGHT
HOLDERS OR THE CONTRIBUTORS BE UASLE FOR I>Hf DIRECT, INDIRECT.
INODENTAI., SPEOAL. EXEMPlARY, OR CONSEQUENTIAL DAMAGES
PNO.UDING.
BUT NOT UI.IITED TO, PROCUREMENT OF' SUBSTITUTE GOODS OR SERVICES,

j
LOSS
OF USE. DATA. OR PRORTS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
D -
ON I>Hf THEORY OF UASIUTY. WHETHER IN CONTRACT. STRICT UASIUTY. OR
TORT ONO.UDING NEGLIGENCE OR OTHERWISE) ARISING IN I>Hf'WAY OUT OF
THE
USE OF THIS SOFTWARE. EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE. •
Do you accepe Ill the lennt ollhe poeceding Licence~? If you~ No, Setup
wl close To instal Hugo98, you II'IUII accepelhio ag~eement.

<lack tio

Recomand sa cititi cu atentie textul de mai sus pentru a decide daca


Hugs98 corespunde a~teptarilor dumneavoastra. Apoi dati click pe
Yes.
C'hoosl' Dl'stination Location £1
Setup .... nstall Hugs9B illhe loloW"'!! ktier.

To nstall to lhio folder, dock Ne14.


To nstalllo a dlferenl lolder, dock a - and ftllecl anothet
ktier.

You can'*- not to r.tall HU!I$98 by c:ic:l<ilg Cancel to eiCit


Setup.

r Desmation Folder
I C:\talgs98 BJOWM.-- II

in continuare e suficient sa apasati pe butonul Next.


0 versiune de Hugs cu mai multe butoane pentru a actiona mai u~or

0 introducere fn Haskell 98 prin exemple - 272 - Dan Popa


SriPrl I ompollPilt• 13

r.~
r No

<lack Cancel I
comenzile uzuale poate fi instalata selectand Yes In fereastra
urmatoare.

Do .\'OU wart 1o nta1 'Huga for wmc-· rwr~~ugaJ?


W..,..-q. Thil PIOIJoWnll ~ wilh- eljler!IIOf\t
and it nat IIICOIMlei dec! for teiiOUI Ule.

rYes

De asemenea puteti opta :?i pentru a instala sau nu sursele Hugs98.


Veti constata ca In meniul Start -> Programs se va crea o locatie
pentru Hugs 98:

0 introducers Fn Haskell 98 prin example - 273 - Dan Popa


Oo,.,.._..,_.. .....,.._coclt?

<led< lftot ) c.rc.~ I

Schimbati sau nu numele $i dati un click pe butonul Next. Va fi afi$at


un rezumat al optiunilor dumneavoastr~ de mai inainte.

St.ut 1 "1'""1( lol~' rJ

Daca n-ati gre$it nimic veti apasa pe butonul Next pentru a continua.
Altfel, folositi Back o data sau de mai multe ori pentru a revizui
optiunile precedente. Butonul Cancel i1 veti folosi daca doriti s~
abandonati instalarea. Altfel urmeaza copierea fi$ierelor .

0 introducere fn Haskell 98 prin example - 274 - DanPopa


Dupa ce indicatorul ajunge Ia 100% apare imaginea urmatoare,
indicand o instalare reusita. Nu va ramane decat sa actionati butonul
Finish.
Ca urmare a instalarii se obtine un dosar cu fisierele urmatoare:

0 introducere in Haskell 98 prin example - 275 - Dan Popa


Hugs.exe este programul executabil pentru lucrul in mod "consola",
mai rapid dar putin mai incomod deoarece presupune sa invatati
comenzile Hugs, printre care :1 :i :quit :reload etc. WinHugs.exe are
in mai mare masura aerul unui IDE (Integrated Development
Environment - mediu de dezvoltare integrat.) Hugs.exe va prezenta
un ecran mai spartan dar va rula mai repede. lar WinHugs.exe va
parea cava mai prietenos.
:: II ug) 9S foJ Wmdo1" . .(ii) I:J
[lie EdJI BuD Qpta0111 Dr-lt litlp

~ II 1111 I I I - I-II II Bugs 98: B~ed on the Bask~ll 98 standard


l..!.lJ II_IIII_IIII_II _II Copyrlgh& (C) 1994-1999
fft1 11---1 I II World V1d~ Veb: bt&p:/lbask~ll.ocg/hugs
li!!J II II Report bugs to : bugs-bugs@baskell.otQ
IIi II II V~tnon: IJoveab~t 1999

~ B~t~ll 98 aod~: Rc~tett v1th coaaand ~~~ opt1on -98 to ~nebl~ e~tens1oo~
~ R~ading tll~ "C:\IIUGS98\llb\Ptdud~.ha'':
~ Hugs ~~SSlOO tot:
~ C : \HDGS98\11b\Pt~lud~.bs
fAI Typ~ : 7 toe belp
1!!!:1 Prelude>
(mJ
I
w
~
~
!!!!

Semnificatiile butoanelor se vor revela atunci cAnd duceti cursorul


mouse-ului in dreptul fiecaruia. Ele sunt in ordine: Help, Script
Manager , Reload Script file (echivalent cu comanda :r care
reincarca un fi$ier reeditat $i salvat iara$i), apoi tripleta Copy, Cut,
Paste urmata de Clear Selected Text $i Evaluate Main Expresion.
Stop-ul opre$te executia programului curent. lmediat sub Stop este
un buton pentru pornirea editorului Notepad, cu care veti edita textul.

0 introducere in Haskell 98 prin exemple - 276 - Dan Popa


Urmeaz~ acel buton care v~ ofer~ optiunile Hugs pentru un "reglaj
fin", inclusiv pentru alegerea altui editor. (La nevoie, schimband
numele programului se poate inlocui Notepad-ul cu un alt editor,
preferat de dumneavoastr~.) Acelea$i op~uni se pot vedea $i
folosind comanda ":set". (Observati c~ toate comenzile Hugs incep
cu simbolul dou~ puncte.)

AI doilea buton de jos v~ arat~ o schem~ a ierarhiei de clase - foarte


util~. lat-o:

Jl.euP1.oat.

P1.oat.in.g

II'W\Ot.or J

l!&onadl

Observati de exemplu ca Real $i Fractional provin din clasa mai


general~ Num, lucru de a$teptat deoarece $i numerele reale $i cele
fractionale au o serie de proprietati ale numerelor generice.

0 introducere in Haskell 98 prin exemple - 277 - Dan Popa


Exit the interpreter : quit

The · qu1 t. command l erm,nates I he cu r~ ant Hugs aoss,on

0 serie de informatii utile veti obtine folosind primul buton de sus,


care deschide Help-ul, veritabil manual electronic. Printre ele este ~i

o lista a comenzilor Hugs.


Acela~i manual electronic it puteti deschide ~i cu un click pe figurina in
form~ de carte cu numele hugs.hlp din dosarul unde este instalat Hugs .

..:. Hues IJS L sN \lanual ... r-11::1

List commands :?

The 7 command dJsplays the foUOWing summary of all Hugs commands

Prelude> ?
l.IST OF COIOL\NDS lny =aAaru:l aay be abbreva~ed ~o c vhere
c 1s the hrs t character 1.ll the full naae
: load <f>lenaaes> load aodules froa spec1f1ed f1les
load c lear al l f1les excep~ prelude
also <hlenaaes> read addH1onal aodules
reload repea~ las~ load coaaand
·proJect <f>lenaae > use pro)ec~ f1 le
ech ~ <fllenaJle > ech ~ fll e
edH ed1 t las~ aodule
aodule <aodule> set aodule f or evalua~1ng express1ons
<expr> eva lua~e express1on
type <expr > pr1nt ~ype o f expre$$10n
? d1splay ~b1s l1st o f coaaands
s et cop~1ons> set coaaand line op~ 1 ons
set he lp on coaaand l 1ne opt1ons
nMes [ pa~ ] l1st naaes currently 1n scope
1nfo <naaes> descr ibe na•ed ob)ec~s
browse c-.odules> browse naaes defined 1n caodules >
f1 nd <na..e> ed1~ aodule con ta1ning def1n1 t 1on of naae
: Ico1Uland shell es~pe
cd d1r change directory
gc force garbage collection
ve rsion pr1n~ Hugs vers1on
qult ex1t Hug s 1nterpreter
Prelude >

0 introducere in Haskell 98 prin exemple - 278 - DanPopa


Hugs.hlp (care are obi~nuita figurina in fonna de carte) va fi u~or de
recunoscut.

j Bact.
~ . . . r:
fU ldil il.w Qo ,....,.... li•lp
~ ~ ~)
)<. ,
Adchul ~~
,.,.; ' ) . ! cw
-
lApp
- p- Undo o-.
3
LJ :___J _] LJ ~ r:l
.demoa. doco tCCn& lib IIC hugs-

~
'""'ot hlp
~ ~ ~ 0 ~
Instal l.JCeme RO<Idme rtmhugteKt u,,., ''"'

m
..muo·-
fii obJed(•) l£LJ$UB ..'1.:1 ,..~ -A.

I ~·
,,
~~

0 introducere in Haskell 98 prin example • 279 · DanPopa


.,

~;
Anexa B: lnstalarea Hugs pe un sistem PCLinuxOS

Unele distributii Linux nu includ Hugs 98 pe discul oferit in vederea


instaiArii. in acest caz aveti nevoie de acces Ia internet pentru a
descarca pachetul. 0 asemenea distributie este PCLinuxOS 9 care
nu oferA amatorilor decAt un Live CD de pe care Hugs lipse~te.
Puteti folosi un program cum este KPackage pentru a descArca
pachetul. Synaptic este o altA variantA de luat in considerare.

Aveti putin de a~teptat pAnA c~nd programul afi~eazA lista


pachetelor disponibile. De pe aceastA lista alegeti ~i bifati Hugs 98
din grupul Development subgrupul Others.

~. hug~98
..--,....---- - - - - -...

!
,._..:.;;-=-..'l<nji\X _ __
N9'< -
H 9<<- "'"".._.,.,
...,...
' " " , . , _ ~ <~P.J v• .... vtl ~"•. ,
H~

.,._.,_
Ncl~~_...,.
.....
w-e~" )OOJ\U0~41

~- ..
H 911><-~
4• t cripdeft • H,nblftf'fJM"'f(H ~ """'.ll"fWnft9 ~ f.ol ~( ...
0 "'"*'"'..
Nq~tJC'J'II'lf"oll.flr
·~~P"'9f<VM 1"his ~~W' ts ...~,~ ....,..li.\t..l91

.--.ll.dtltq MGI'-...d ,..,. •«•d qotlWll- '-"""fPn .Cnc:tllft\ ••*Atoortt. ..,.lllj


N~~P<• ~ ll'l~len tCOI'I'InP~~wlfhtNibr..... \~ll'ltt'w
Hqoo-oloq mo1.1 r~t clf'll: ~ Qt U. k.a.sJ.d lAw¥Y fteport M\d Mh ••1• tian litlfMWt
"""'*"hlltte C~ ~(iU(.lO
N9U'1<
M§IHI
•pn-'lbt'•~~~H.w(lPirfb) (<: ~ o.n •1m11biC~e e-u.-dF-ItN""""''
1<•10 4U
lbc \04.~ ou. 61(!l.•C_l ol.hbr \oMrat_i U.lrbc ~c fi·C"-*. 1 2\
litK uuHc.t,ec...J.n llbdt,o.l, llbdl.sc- :i(;(AK ..l 01. 'bell $0 lfGt.•c_2 l '·
ill:wH \ 0 6 . •bm toMGU8C_1 01 l·bnc:.Uf\oti..» \, ltlr if~lif \.0 AJ
N kb»Pf:llU-0.-ve:l
N•bbk2..,....et
N•b<.ttmo~...s. ... ._l m aintain• , "'"' <PilC.t~n.lnct~• •<dtcCJM>
N~~><ml fit.,..,... hu9i91·:0021 120· )1Td. 1566Jptn
N•bum)..dtrv~ ln~h..d . ... •J6•
N •bcon\o&to-dt..,f'll
N~Ofi)OiitO t;lt(~cJ.v..

Se pot selecta mai multe pachete, dacA aveti nevoie, cu un click de

0 introducere in Haskell 98 prin example - 280 - Dan Popa


mouse pe punctul din dreapta numelui. De exemplu v-ar putea
interesa Happy, un generator de parsere Haskell. Pentru a incepe
msta area ap~sat1 pe butonullnstall

•:=~: 1 APT: Oeb,l~


. .-~-.,:::: :~:-
-. c::.'::-.,:-=
; ~:-·-: ,: ,.: :. ,: :,: :. .,:-.,:::,:-:::.,-:-,-,:::.,::-,-:.,-:-,-,-:-,.:-:::,:.-,::,,-: ,,::.-.-=-~::.-:-.::,::":::":-:,.-:_,-::,-------,
..~(l a n (l "•t "~ Lh h Oat••
t.u llifiiiQ o-p-d#truy , ,... Don•
T~ o I OUO.. l ft0 I«• p.tt. l •.qn WI H btr ~ nt I t UM
tlllt. . .
-. ,.,., .._~, .,M.,....,_<~~. 1 ~'~ •otlv ,,.,,•,.1l • d , I r•"""-" ....,. • ao• "f-9-"....WI!'I
..,..,.. t o 041 ll'S)Io:a o f •rtJIIv••
AIUt """'"' U"9 ·U•o"' o f oldi.lll_ .. ~ dh .. \J".l • · • " be lf\ed
Gorot I f15f /t\~ ntvu; •t JK\I•Ufl..,../). . .1 ~ tr.w7'"- z..t:tJH 'kodlo I U 'U t.:& l
"' It ......, " ,...,.., 1.»1'-A . ,

I
-·-
Qo'III\'III!Nd Of'M)'
tt• ~.....

_. .....
K AJ..,._ YH
I•• t• ,. ~·~

l '-"' 1

lnstalarea se va desf~$ura ca in imaginile al~turate . Dac~ ati bifat


Download only, comanda de instalare nu va face decat s~ descarce
pachetele pe computerul dvs.

lnst.ll: 1 APT: Dablan Pac kage

.......
lrao<AO<S • • · ~,-, DleJ.._.,. fltONTIJC). ~~~Pt ~· •• ,t.-lt
11•.-4• .... ill' ~ik.a9e U'JU
....,.l"4 ~"'"''"' 1ret
0..
Olw\e-
.,., ' ••I"M , n•o II SUl.T-\?

,.,. tvl\ . . anv HI • ~ ~, ot!l \ b. '"•h\\•d


~ .. ~,.
• iJ"t .._....,.., llf19f ...Wod I •- 1, ln,r .. tt .. tt • • ~..,.. .t •d - d . 1\0I ....9,..,,._4
Wttfltf 10 91'* IJ" Ika ()f ~f t llt 1••'L
Af lott '-1 1UJ • .JI66•:& o f • drhtHIII.A1 1t 11ol. •P• (4 .. tt l b-e ..... d
t fl .....f

Ge t J ll p ltftiJ f'h.1" 9 n t pt.\ln"AU'II.l. .<t /C.'l ~.,,, ... 1 . .2 112• .MttU.. IU.U UI
• •ttl'i"'•l U\l t..l 111 • J .) Ill e•.A.r.)•
( ~ Uu\liiQ · p" I ll¥" 1
"• •r.ar '"4

--
---
1110_...

_. .....
., ,.,...,..,....
l"~ Cdo ,_ ur'llmt.d)

0 introducere Tn Haske/1 98 prin example- 281 - Dan Popa

------
Install: 1 APT: Deblan Package
• • ,.pof\ DUlAN rtOHTUI>; · t· 9f:l hl't•t\ · · Yn hug1>9S • ..c:ho aEWLT•\1
•~...ct1n9 '""Uft l.nh . • Doa~
kt1dug OtpH4euy " " O.•
fht to\\owing HE• P«k.atn • i\\ be lAU-~i\\c-d ;
llt.ttll
I p.aC' ..Ii9U Uptr~1Jtd. l ft('o~\y l.SUtl t(l I t('IIIOVed .and I Ut .p4)l.t!lt'd
......,.. ' • 911 115n1 ot arc h""
11Af hr u~'t•q 4)Mkl. of .odtUon•\ csu\. lP«It vlU bet •~C'd .
Wt : l hD:/Ittp .rl•"9·• t pc: h ne:tM/ 208VO\ • •9"98 210~lt1e- lllrd\ I12SH.IJ
rtuh t4 usns u ' ' ' ue I'S/J I
hec" ll"'9 IUifl I li~ l . ••
' ' IJNI""9 •

oo-...w,

.....aJ._
110-

191\0ttb01d
X 6U\Imtyft
1Htl4onocUI1in>t.. l

lnst•ll: 1 APT: Debl•n Package


~~s ~.-.-.p-.-,.-~-,-,.-.-~-~--~~--,-~-~·-;•_t_i-~-~·-,-\--yn ••
--.--~-
,,-.-,-~-··-~-
,W
--LI--~-
,----------~
hug\o91 ••,.duq r.c "-'9• Lu u . Don•
euHdl"9 ~Cle:n cy tree oont
Th fo\\owinq NEWpftk~t v J. \1 br in•t•lhd:
••9•91
I P•tY9ft 11pqrtdtd. t ntw\y intt•\\td, 8 r~,..td Mid e not t~PQtao.d ,
NWd to g.t tn71...a of . u r.t ... H ,
After unp.Kk!nv .tJ66kl of •ddhton•l duk sDol(• wttt bt ~;~sM
"'' I l\1> ://flp. n\vug .o\ P<ll•••ostm•t.. ••110M ~2 1 12t · Jid~ lrnH.II
Jet<~od rnna ln Us 128.8kl/sl
[A.CUI U 9 lPft t •lMt) .
PrepAn"g ,,.. ,.,,,.,_,....,.,,.unn~n t••'"••n•••u hl (l..,l
lM\J
· ·~·~··
U ..,,.,,..,,.,UtiU-U #tiHIUihiUI..Ikltll (
•£SUlT4

~ ... "'
II0--
'9N>'•ll)<'"'9
'9"«• bOld
X 6J~)'H

I•" 1110 noc .....,.....,

0 introducers in Haskell 98 prin example - 282 - DanPopa


Anexa C:

Instal area Hugs din distributia Mandrake 10.0 pe o ma~ina Linux


Mandriva 2005.

il puteti instala cu u~urinta pornind de Ia pachetele in format .rpm.


Din nefericire Mandriva 2005 versiunea de download nu contine
aceste pachete. Dar puteti folosi pachetele distribuite impreuna cu
Mandrake 10.0 (Nu uitati ca Mandriva 2005 este noul nume pentru
Mandrake 10.2).
Aveti de instalat intai biblioteca libreadline.so.4 de pe CD-ul cu
numarul 1 apoi hugs98 de pe CD-ul cu numarul 3.
~-.. 9-'llH)2006-10 rlltc>rprr>toa rl' ·HUGS- K• [!JEJ
Location Edit Yiew ~0 aookmarks Iools
S,ettlngs Window J:ielp

·I QQ Q[0_L lt'l-1~ »
·I G LQ.cation: ~ 5- l 0-interpretoare+lUGS
•g
' J
...
=- --=--

~
(pii\ (pii\
../ "
hugs98- libreadltne4-4 .3-
20021120-3m ... 7mdk.l586 .rpm
IMonParsing.hs - (5.6 KB) Backup File

lnstalarea se poate face cu click pe fiecare fi~ier. Asocierea .rpm-


urilor cu programul de instalare este facuta. Sau puteti folosi batranul
~i incercatul Midnight Commmander. lnstalarea in acest caz
decurge Ia tel cu cea din Anexa D.

0 introducere in Haskell 98 prin exemple - 283 - Dan Popa


Anexa D: lnstalarea Happy pe o ma$in~ Linux Mandriva 2005.

il puteti instala cu U$urint~ pornind de Ia pachetul In format .rpm. Din


nefericire Mandriva 2005 versiunea de download nu contine acest
pachet. Dar puteti folosi pachetul distribuit impreun~ cu Mandrake
8.2.

Instal area decurge In mod grafic, caIn imaginile urm~toare:

l,ocatton .Edit :l{.lew yO B.ookmarks Iools Settings Jttlndow lfelp

·Q Q o J0 f "· ~ I I~~ ~I C1 ~ !Q] JJ t1.


~~ ~ L2cation: ~ file:/mntJcdrom21MandrakeiRPMS4 • g
5·2mdk. rQ P
halfd-gul-2.10·1mdk.
noarch rpm
tQ
P
hld~_ld·1.50b2·lmdk. Qp
no11rch rpm
·

0011103·
.rpm I;;;;;
P
halfStalS·1 .0·0.b7.
1mdlc.noarc h.rpm
Q
P
hld~_ld·cgi·l.50b2·
lmdk .noarch rpm

lient-1 .41 . '1 tPI•Y 1 11 ,.'tnrJk r--.. hlds ld-frPnc h -1


fpn'l -
16rpm

-
~HI.J r ~1rl, 50b2·1mdk.noarch ....

1mdk
,n
~
rp
helptool-2 4-14mdk
no.:Jrch.rpm
-
Q hplng2-2 o 0 ·0
beta54.2mdk.i586.r ..
~

, 10-1mdk Q -
P
hevea -1 05 -Smdk
t586.rpm
Q
P
hsftp -111 -Jmdk 1586
rpm
Q
I"

!hDPPy-l.ll -2mdkt586 rpm (231 8 KBl RPM Package Ftle

Dati click pe pachetul happy-1 .11-2mdk.i586.rpm din directorul


RPMS4.

Vi se va cere sa precizati operatiunea dorita. Alegeti instalarea.

0 introducere in Haskell 98 prin example - 284 - Dan Popa


'lbu are about to install the following software package on your
computer:

/mnUcdrom2/MandrakeJRPMS4/happy-l .l l -2mdk.i586.rpm

'lbu may prefer to just save It What IS your cho1ce?

Install j; ~ancel

Apoi, dac~ nu sunteti administratorul de sistem, vi se va solicita


parola de root:

· -~ Que•y g fJ
'lbu are attempt1ng to run a command wh1ch
requires administrative priVIleges. but more
information is needed in order to do so.

Password for root • • •I

(/;QK

Apoi instalarea i ncepe:

lar terminarea ei este semnalat~ cu mesajul:

0 introducere rn Haskell 98 prin exemple - 285 - Dan Pops


. _.. RPM lll5ti'lllatiOI1
Installation finished

Qone

Bineinteles, mai puteti instala pachetul $i cu utilitarul RedHat


Package Manager (comanda rpm) sau cu vechiul Midnight
Commander, care $tie sa deschida arhivele .rpm printr-o simpla
apasare pe Enter.

lntrati in arhiva apoi alegeti scriptuiiNSTALL $i tastati ENTER.

0 introducere in Haskell 98 prin exemple - 286 - Dan Popa


Anexa E: lnstalarea Hugs pe sistemele Ubuntu.

Daca pe sistemul dumneavoastra Ubuntu Linux (sau un derivat de-al


lui) ati folosit o comanda cum ar fi:
$ sudo apt-qet install synaptic
~i acum dispuneti de programul de instalare Synaptic, il puteti folosi
pe acesta pentru a instala Hugs. Remarcam totu~i ca ar fi fost Ia tel
de simplu sa fi scris de Ia inceput Ia promptul $ :
sudo apt-qet install huqs .

AU s Packa~
Am.lt~ Radto (unowrs~) • hugs 91.zooeot.21-S.~ • zooeot.2H
Communocallon (" tmakt 3. 14-lubuntu1
Commo..roicat•on (multl-s (l h.uk~ll-modt 2.8.~1
Communication (uni~rst) 0 llbghc-src~xts-doc 1.11.1-3
Closs Platform 0 Ubghc-src~•LS-dt<o~ 1.11 1·3
Ctou Platfo;m (multlwrst 0 Ubghc-src~xts1)fof 1.11.1-3
Closs Platform (~st) 8 bbghc-e<IISOn-<OrMO< 1.2.1.3-8
DatabasM
DatabasM (UiliYffSt) A Huketl 98 i nterpr~~r
Dtbug
Ct't SU~nshot G~ Cha~9
Odlug (multl-st) ~
Debuq (UI\I~rst) Hugs Is ~n int~rpr~t~ for the non-stnct. pur~ly runcllo~l
programming
langua~ ~sk~ll This ~rslon of Hugs. Hugs 98. supports ntarly all
S«tions of
~ ~sk~l98 sp~CJfotton. as ~u as a numb~ or txt~lons
status

Ongon Tht ~skelt langw~ Is dHCn~ by docum~nts 1n ~ hasktlkloc


p.1cka~ Other bbrariM art docum~nt~ tn the ghc~oc p~cka~
I CUstom Filt~s

L ~arcll ~suits

I Architecture
10 P<lW~ list~. 1797lnstaU~. o broktn. o to lnstaiVupgr~~. 0 to r~mo~

cauta~-1 pe hugs, bifati-1 ~i dati un click cu mouse-ul pe Apply.

0 introducere in Haskell 98 prin exemple - 287 - Dan Popa


1

Anexa F: Proprieta.ti algebrice ale monadei parserelor - caz particular


de monada. cu "plus" ~i "zerou"

Operatia ' mplus' ~i parserul mzero se conformeaza. unor legi


algebrice:
mzero ' mplus· p =p
p "mplus· mzero = p
p ' mplus· (q "mplus· r) = (p 'mplus· q) ·mplus· r
Aceste proprieti au loc (trebuie sa. aiba. loc) 1n orice monada. cu
zerou. Ele afirma. ca. zeroul este element neutru Ia "adunare" ~~ ca.
"adunarea" este asociativa.. Demonstratia e imediata.. in cazul nostru
sunt adeva.rate fiind induse de proprieta.tile listei vide 1n raport cu
adunarea listelor via definitia lui · mplus' . 'l

0 ++ p =p
P ++ D=P
p ++ (q ++ r) =(p ++ q) ++ r
Exista. de asemenea o serie de relatii (proprieta.ti) care leaga. zeroul
mzero ~i operatia ·mplus· de bind(>>=).
In
Exista. situatii (de exemplu acelea cand rezultatul parsa.rii este o lista.
cu duplicate sau situatia cand ne intereseaza. sa. ga.sim doar o
singura analiza a unui ~ir) cand avem nevoie doar de primul element
din lista de solutii oferita. de doua. parsere, care altfel s-ar "aduna" cu
' mplus·. Se introduce atunci un alt operator de adunare, de data
~ aceasta determinist (1n sensul ca. parserele adunate astfel produc o
n
n lista. de cardinal eel mult 1).

0 introducers fn Haskell 98 prin example - 288 - Dan Popa


n

n
Anexa G: Schema interpretorului monadic

Deoarece o imagine poate spune mai mult decat o mie de cuvinte am


desenat schema (o schem~ posibil~) a interpretorului monadic al unui
limbaj (sau al unui format oarecare de text, dat printr-o gramatic~) .

El este compus din: Parser, Termii sintactici (sunt declarati ca arbori),


Valorile termilor (adesea formeaza un tip reuniune) , Printer-ul (functiile

0 introducers in Haskell 98 prin example - 289 - Dan Popa


1

folosite pentru afi~area valorilor) ~~ bineinteles monada. Semantica ne d~


valorile termilor. La implementare se indic~ modul de calcul al acestor
valori prin succesiuni de operatii din monad~ (conectate cu bind) sau
scrise direct in do-notatie. Environment-ul nu e reprezentat.

Retineti: Atunci c~nd proiectati un interpreter monadic scris in


Haskell trebuie sa aveti in vedere ca el este compus din:

- Parser
- Declaratiile termilor conform sintaxei
- Monada - cu ajutorul ei se face trecerea de Ia semantica asociata
sintaxei Ia valori. Ea este modelul de calcul, serve$te Ia
implementarea ma$inii virtuale $i poate fi schimbata cu alta tara a
modifica dramatic interpretorul.
- Multimea de valori care rezulta din evaluarea termilor
- Printerul - face operatia inversa parserului
- Environment-ul - nereprezentat in imagine - stocheaza valori ale
variabilelor (uneori stocheaza doar indicele pozitiei variabileleor
pe o stiva, daca a$a o stiva exista). Este similar cu lista de
variabile din LISP.

0 introducere in Haskell 98 prin example - 290 - DanPopa


Anexa H: Cal/ by value interpretorul din finalul cap. 6 in do-notatie
-- Dupa. P. Wadler - The essence of functional programming pg 4

Foloseste Monada calculelor cu mesaje de eroare .


Aceste calcule sunt de alt fel, dau mesaje de eroare.
Elementele care formeaza monada sunt
-- sau calcule terminate cu succes
-- sau Error urmat de stringul cxplicativ

-- Refacut cu clasa Monad din Haskell 98 si cu do-notatie


-- Dan Popa , 31 ian 2007 , back-end-functional-do.hs v.03
-- Despre comentarti:
-- Nu folositi egaluri i n liniile de separare

module Backend v03 where


import Monad
-- Back-end de interpretor rescr1s in do-notatie
-- dupa Introd in Haskell 98 prin exemple, draft-ul din 2005
-- la randul lui dupa lucrartle lui P.Wadler
-- Fisier : back-end-functional-do.hs

-- Limbajul de interpretat: arborii AST------------------ ------


-- o redenum1re de tipuri pt. a fi mai sugestiv
type Name = String

--Termii sunt :
-- variabile care au nume,
-- constante care au valoare Intreaga
-- suma de alti termi
-- o lambda expresie care contine un nume de variabila si un term
-- aplicarea unui term asupra altuia
-- (restrictiile ca primul sa fie fct. sc vcrifica la executie)

0 introducere Fn Haskell 98 prin example - 291 - Dan Popa


data Term Var Name
Con Int
Add Term Term
Lam Name Term
App Term Term
deriving Show -- v0 . 3
exemplu:
tl -
(App (Lam "x " (Add (Var "x" ) (Var " x " ) l) (Add (Con 12) (Con 69)))

Interpret orul foloseste o lista de asociere


pentru a memora valorile va riabilelor
(similara cu ALIST-ul de la interpretoarele LISP)

type Environment • [(Name , Value) 1

Valorile din aceste pe r echi sunt


Wrong - valoarea care inseamna eroare fatala
Num Int valo r i care inseamna rezultate corecte , numere
intregi
Fun (Value -> M Value)
- semnificatia unei functii este o computatie
o valoare monadica de forma unei capsule etichetate
cu Fun si avand inauntru o functie de la Value -> M Value

data Value • Wrong 1 Nuro Tnt 1 Fun (Value -> M Value)

--- PLUS:
unNum (Num i) • i
unFun (Fun f) • f
[u

-- Valorile se afiseaza cu functia:


showval ::Value-> String
showval Wrong • " <wrong>"
showval (Num 1) - show i -- show 1 in loc de showint i 1 1

0 introducers in Haskell 98 prin example - 292 - Dan Popa


showval (Fun f) •"<functie>"
inclusiv cand sunt incapsulate monadic si le afiseaza
showM

-- Definim un tip s1milar cu monada cu erori


-- un calcul poate fi cu succes sau cu eroare si mesaj
data M a • Succes a I Error String
-- nu folositi " newtype" aici II

-- il declaram ca instanta a clasei Monad


instance Monad M where
(Succes a) >>• f • f a
(Error s l >>• f - Error s
return a • Succes a
fail s - Error s

-- Afisarea valorilor monadice, din capsule


sho·.-M (Succes al - "Calcul cu succes: "++ showval a
-- scrie ca a avut succes calculul si plascaza conversia
-- functiei care transforma in string-uri reprezentar1 de valori
-- adica functiei showval
swowM (Error s) = "Eroare ! "++s

-------------- Operatii cu valori monadice ------------------


-- ll Cautarea in environment returneaza o valoare monadica
-- care poate fi ceva gasit pe lista
-- (care mai e si incapsulata monadic l)
-- sau un Error s (unde s este stringul explicatl.V)
-- generat cu functia fail
lookup£ .. Name -> Environment -> M Value
lookup£ x [] - fail ("Variabila nedefinita: " ++ X)

lookupE x ((y, bl :e) - if X


-- y then return b
else lookup£ x e
-- a pel recursiv
-- pentru continuarea cautar11

0 introducere in Haskell 98 prin example - 293 - Dan Popa


1

:)
-- 2) Operati~ cu valori si rezultat incapsulat monadic

add .. Value-> Value-> M Value


add (Num i l (Num j) • return (Num (i+j))
add a b • fail (" Nu sunt doua valori numer.ice :" ++
showval a ++ showval b )

-- varianta:
-- Fireste case mai pot adauga si alte operatii ...

--3) Aplicarea unei functii

apply .. Value -> Value -> M Value


apply (Fun k) a • k a
apply f a - fail ("Ar trebui sa fie functie: " ++showval f)

----Interpretorul -propriuzis------------------------------------
interp .. Term-> Environment-> M Value
interp (Var x) e - lookupE x e
interp ICon il e - return INum il
-- scoate numarul din arbore si-1 face (cu
-- return) valoarc numerica impachetata monadic

interp (Add u v) e • do { a <- interp u e; -- interpreteaza a


b <- interp v e; -- interpreteaza b
add a b I -- suma monadica

interp (Lam x v) e • do { return


(Fun (\a -> interp v ( (x,a) :e) ) I'~

I I

interp (App t u) e - do { f <- intcrp t e ;


a <- interp u e ;
apply f a )

0 introducere tn Haskell 98 prin example - 294 - DanPopa

:
-Testarea---------- ------------------------------------------ ----
run :: Term-> String
run t ~ showM (interp t [) )

ln imaginea urmatoare se poate vedea executia unui program reprezentat de termul t1:

r.lra' x
SesSion Ed1t VieN Bookmarlcs Serunos Help

App (Lam •x• (Add (Var •x•) (Va r "x"))l (Add •


(Con 12 ) (Con 691)
Bac kend_v03> run tl
"Calcul cu succes: 162"
Bac kend_ v03> I

Executla unul program dat sub forma de termulul t1 .

0 introducers fn Haskell 98 prin exemple - 295 - DanPopa


Anexa 1: intrebari de control (selectate)

Daca dupa citirea unuia dintre capitolele eartH nu puteti raspunde Ia


intrebarile de control din sectiunea corespunzatoare, ei bine, ar fi cazul sa
recititi capitolul. Apoi, daca nu v-ati lamurit va invitam sa vizitati ~I site-ul
comunitatii.

lntroducere:

01: Din ce familie de limbaje face parte limbajul Haskell ?

02: Ce intelegeti prin sistem de tipuri ?

03: La ce ne ajuta paradigma programarii generice - "generic


programming" ?

04: Ce sunt listele polimorfice ? Dar arborii polimorfici ?

05: Ce este o clasa in limbajul Haskell ?

06: Ce intelegeti prin "Haskell este un limbaj cu layout." ?

07: Ce avantaje prezinta Haskell-ul penttru cei care lucreaza cu structuri


de date pe care le modifica frecvent ?

Functii ~~ aritmetica:

014: in limbajul Haskell se poate programa in stil imperativ ?

015: Ce este do-notatia ?

016: Exista in limbajul Haskell pe linga clase ~~ instante de clase ?

017: in cate randuri de program Haskell ati scrie un algoritm de sortare ?


De ce are aceasta dimensiune ?

018: Cum elimina Haskell-ul acele paranteze multiple care existau in

0 introducere in Haskell 98 prln example - 296 - Dan Popa


LISP?

019: Ce tipuri de date elementare manipuleaza limbajul Haskell ?

020: Cum se declara, cum se numesc, in Haskell, numerele intregi ?

021: Cum se scrie o functie simpla in Haskell ? Exemplu.

032: Cum se noteaza "adevarat" l?i "fals" in Haskell.

033: Cum declarati o lista de caractere ? Dar un string ? Este vreo


asemanare ? Este vreo deosebire ?

034: Cum se numel?te biblioteca standard a limbajului Haskell ?

035: in ce a fost i mpartita biblioteca standard?

036: Exista in limbajul Haskell un tip al numerelor naturale ? Dar al


numerelor complexe ?

037: Ce este "()" in limbajul Haskell ? Un tip sau o valoare ?

038: La ce 1-ati putea folosi pe acest () ? La ce este el folosit de obicei ?

050: Ce tip are operatorul "II" ?

051: Un operator are tipul [a 1 - > lnt - > a . Ce ar putea sa extraga


dintr-o lista (infinita) un astfel de operator ?

056: Ce raspuns capatati din partea interpretorului Hugs sau a


compilatorului interactiv GHCi Ia intrebarea:
:t (:) ?

057: Cum se scrie operatorul "diferit" in limbajul Haskell ?

058: Dar eel de aparteneta ?

059: Ce nivel de prioritate are adunarea ? Dar operatorul ":" ?

060: Dar disjunctia l?i conjunctia ?

0 introducere in Haskell 98 prin exemple - 297 - DanPopa


Despre tipuri:

01 00: Notiunea de triunghi descrie sau nu un tip simplu ?

01 01: Cu ce tel de litere lncep numele constructorilor de tipuri din


declaratiile data ?

01 02: Cu ce tel de litere incep numele de functii ?

0103: Ce intelegeti din afirmatia 'Tipul triunghi este polimorfic." ?


1

0104: Constructorul de tip este o functie sau nu ? Dar eel de date ?

0105: Cu ce tel de functii veti folosi pentru a face conversii de Ia tipuri


simple Ia tipuri de date compuse ?

0106: De~i In scrierea lor nu se foloseste nici o majuscul~~ [] I () I ~i ->


sunt ... completati dvs I

0107: Cu ce simbol se separ~ constructorii de date alternativi ai tipului


utilizator reuniune ?

Structuri de date infinite ~~ alte notiuni utile:

0119: Ce este ":" ?Constructor de date sau de tipuri?

0120: Cum ar trebui s~ fie declarat constructorul infixat al listelor "···"


pentru ca 1:::2:::3 s~ fie o list~ ?

0121: Prin ce difera infix/ de infixr? Ce inseamna aceasta diferent~ ?

0122: Scrieti functia care extrage elementul sau elementele din mijlocul
unei liste de lungime mal mare sau egala cu doi .

0123: Daca intr-un program s-a scris:


atoi 1 = va1 1 i ( revers 1)
cum ati simplifica ecuatia de mai sus ?

0124: Cu ce cuvant cheie se declar~ tipurile sinonime?

0125: Definiti tipul coad~ cu constructor infixat, prin analogie cu tipul

0 introducere in Haskell 98 prin example - 298 - Dan Popa


list~?

0130: Declarati lista numerelor naturale In Haskell.

lmplementarea unui li mbaj in Haskell:

0147: Explicati ce este un interpretor .

0148: Ce sunt categoriile gramaticale ale unui limbaj de programare ~i cu


ce nume comun sunt desemnate in teoria gramaticilor ?

0149: Diferenta dintre un arbore sintractic ~i unul operatorial ar fi


completati dvs.

0150: Declarati un tip de date necesar pentru a implementa arbori


operatoriali ai expresiilor aritmetice formate cu numere reale ~i operatiile
obi~nuite din aritmetic~.

0151 : Scrieti o functie de c~utare a valorii unei variabile i ntr-un context


de forma {neuzual~)
[ ( 3 . "x" ) ( 5 . " a 1f a") (1 . " bet a") J .

0152: Cum procedati pentru a declara c~ un tip de arbori utilizator va fi


afi~abil pe consol~ ?

Monada:

0153: Ce tip are operatorul »=? Dar =« ?

0154: Cine a scris biblioteca Parsec ?

0155: Ce stil {paradigm~) de programare face apella monada starilor ?

0156: Cu ce functie se transform~ valori obi~nuite in valori monadice ?

0157: Explicati ce se va obtine prin evaluarea formulei:


(return (1+2)) »= (print)
?

0158: Scrieti programul care calculeaz~ suma a dou~ numere in do-

0 introducere in Haskell 98 prin example - 299 - DanPopa


notatie.
(lndicatie: folositi monada stMior sau monada de 10)

0159: Transcrieti acest fragment de cod in notatia cu operatori monadici


in loc de do-notatie.
do {a <- t1 ;
b <- t2 ;
return ( a+b ) }

0160: Ce ar fi scris Espinosa in teza sa de doctorat, in sectiunea


dedicat~ operatorilor de lifting monadic, daca ar fi programat in Haskell $1
nu in Scheme ?

Monade utilizate de programatori :

0165: Ce declarati cu ocazia instantierii unei clase din Haskell ?

0166: Prin ce difer~ instantele claselor din C++ de instantele claselor din
Haskell?

0167: Cum este definit in monada ldentitate operatorul >>= ?


( »= ) (ld X) f = ?

0168: Ce tipuri au getChar ~i putChar din Haskell ?

0169: Ce tip are print «Hello World!" 1n Haskell?

0170: Cu ce cuvant Tncep ramurile unui if. ..then ... else scris in interiorul
unei do-notatii ? (lndicatie: presupuneti ca pe fiecare ramura sunt mal
multe randuri de cod.)

0171 : Cum actioneaza operatorul "bindft (>>=)din monada stMior?

Studiu de caz, evaluatorul de expresii:

0196: Cum a fost inventat tipul Parser de cMre G.Hutton si E.Meijer ?


(Tineti cont de felul cum este el definit.)

0197: Cum au definit cei doi autori de mai sus operatiile bind ( » =) $i
return din monada parserelor ?

0 introducere in Haskell 98 prln example - 300 - Dan Popa


0198: Cum puneti In actiune un parser modular din monada parserelor.
(Ind. Nu uitati c~ el este un fel de capsul~.)

0199: Cum se prezint~ parserul modular compus din parserele p1 ...pn


scris:
a) ca o succesiune de bind- uri (> >=)
b) In do-notatie

0200: La ce au folosit G.Hutton ~i E.Meijer operatorul +++ ?

0201: Dou~ parsere modulare sunt puse s~ actioneze asupra aceluia~i


string de intrare in paralelism simulat, p~strandu-se toate rezultatele.
Definiti operatorul care le combina.

0202*: Definiti un operator ca in exemplul precedent dar care folose~te


paralelismul real.

0203: Scrieti eel mai simplu parser, eel care consum~ din intrare un
caracter f~r~ s~-1 testeze.

0204: Ce efect are


parse i tern "text " ?

0205: Prin ce difer~. ca efecte,


parse (item "mp l us · item) • text "
de
parse (item+++ item) " text "
?

0 introducere in Haskell 98 prin example - 301 - DanPopa


Bibliografie:

[Aab-96] Aaby, A. Anthony; Haskell Tutorial


http :1/WWN .cs.wwc.edu/-cs_ dept/KU/PR/Haskell.html

[Aab-**] Aaby, A. Anthony; Introduction to Programming


Languages ,http:l/cs.wwc.edu/-aabyan/221_2/PLBOOK/

[Aab-05] Aaby,Anthony; Popa,Dan; Constructia Compilatoarelor folosind


Flex $i Bison, ISBN 973-0-04013-3, 2005

[Aab-05] Aaby,Anthony; The Lambda calculus; Walla Walla College,


June 29, 2005, (draft copy - WWN resource)

[Ada-**] Adamek, Jirl; Herrlich, Horst; Strecker, George E.;


Abstract and Concrete Categories ;
http://katmat.math.uni-bremen .de/acc/

[Aho-86] A.V. Aho, R.Sethi, and J.D. Ullman; Compilers: Principles,


techniques, and Tools, Addison-Wesley, 1986

[And-05] Anderson, Lennart; Parsing with Haskell; Comp. Sci. Lund .


Univ., 28 oct. 2001- www resource

[Bar-02] Barr, Michael; Wells, Charles; Toposes Triples and Theories, ver
1.1 , 7 nov 2002,
http://WWN .cwru .edu/artsci/mathlwells/pub/ttt.html
ftp://ftp.math .mcgill.ca/publbarr

0 introducere in Haskell 98 prin example - 302 - Dan Popa


[Bar-98] Barros, Jose Bernardo; Almeida, Jose Joao; Haskell
Tutoriai,Dept. of Info., Univ. Minho, Braga, Portugal, Sept.1998

[Ben-**) Benton, Nick; Hughes, John; Moggl, Eugenio; Monads and


Effects - www resource

[Ber-91] Berindeanu, Radu ; Matekovits, Agota, FORTH Concept


lnformatic $i Limbaj de Programare, Editura Facia, Timi~oara , 1991

[Cab-92] Cabasino S, Paolucci P.S., Todesco G.M; Dynamic Parsers and


Evolving Grammars, Univ. La Sapienza di Roma, ACM SIGPLAN Notices
27 ( 11 ), 1992 - www resource

[Cio-96] Ciobanu, Gabriel; Semantics limbajelor de programare


Editura Universitatii, IA~I . 1996 ;

[Cre-01] Crenshaw, Jack W; Let's Build a Compiler- Compiler Building


Tutorial, v.1.8 , 11 april. 2001
www resource

[Dau-04] Daume,Hal; Yet Another Haskell Tutorial; Copyright (c) Hal


Daume Ill ,2004 - preprint version

[Del-93] Delahaye, Jean Paul; La semantique de Prolog,Eurolan,


Premiere Ecole d'Ete Franco-Roumaine sur le Traitement du Langage
Natural, la~i . 1993 ;

[Den-98] Denning, J. Peter; Metcalfe, Robert M.; Beyound Calculation,


The Next Fifty Years Of Computing, Copernicus, Springer Verlag,New
York, 1998;

0 introducere Tn Haskell 98 prin exemple - 303 - DanPopa


[Dup-95] Duponcheel, Luc; Using catamorphism, subtypes and monad
transformers for writing modular functional interpreters, Utrecht University,
16 Nov. 1995

[Dye-**] Oyez, M.C. Luego; Gonzalez, B.M. RodrYguez; A Language


Prototyping Tool based on Semantic Building Blocks , www resource

[Erk-02] Erkok, Levent; Value Recursion in Monadic Computations,(Ph .D


thesis), OGI School of Science and Engineering at Oregon Health and
Science University , Oct. 2002

[Erk-00] Erkok, Levent; Launchbury, John; Recursive Monadic Bindings:


Technical Development and Details, Oregon Graduate Institute of
Science and Technology, June 20,2000

[Ear-70] Earley,Jay; Sturgis Howard; A Formalism for Translator


Interactions, Communications of the ACM, Volume 13, Number 10,
October 1970, p.607-617;

[Esp-95] Espinosa. David; Semantic Lego, Ph. D. Thesis, Columbia


University, 1995

[Fok-**] Fokker, Jeroen ; Functional Parsers; Dept. of. Comp. Sci.,


Utrecht University, Utrecht, Netherlands - www resource

[Fis-02] Fischer, Charles - CS 536 Introduction to Programming


Languages and Compilers - 2002
www resource - indisponibil acum

0 introducere in Haskell 98 prin example - 304 - Dan Popa


[Har-98] Harrison, William.L.; Kamin, Samuei.N.; Modular Compilers
Based on Monad Transformers, IEEE Int. Conf. On Computer Languages,
(Loyola University, Chicago), 1998, pages 122-131

[Har-01) Harrison, William Lawrence; Modular Compilers and Their


Coffectness Proofs,Ph. D. Thesis, Urbana, Illinois, 2001

[Har-97] Harrison, L. William; Kamin, N. Samuel ; Compilation as Partial


Evaluation of Functor Category Semantics, 1997

[Hud-99] Hudak Paul, Peterson John, Fasel Joseph - A Gentle


Introduction to Haskell 98, Yale University, Los Alamos Laboratory, www
resource, oct 1999.

[Hud-99b] Hudak P., Peterson J., Fasel J.H.; A Gentle Introduction to


Haskell 98, Online Supplement, www resource.

(Hut-93] Hutton, Graham; Meijer, Errik; Monadic parsing in Haskell; Univ.


of Nottingham, Univ. of Utrecht
J. Functional Programming 1 (1): 1-000, january 1993, Cambridge
University Press

[Hut-96] Hutton, Graham; Meijer, Errik; Monadic Parser Combinators -

"Technical report NOTTCS-TA-96-4" Dept. Comp. Sci. Univ.


Nottingham - 1996
http://www.cs.nott.ac.uk/Department/Staff/gmh/monparsing.ps

[Hut-98] Hutton, Graham; Meijer,Erik; Monadic Parsing in Haskell Journal


Of Functional Programming 8(4):437-444, July 1998

0 introducere in Haskell 98 prin exemple - 305 - Dan Popa


http://www.cs.nott.ac.uk/Department/Stafflgmhlbib.html#pear1

(Hut-04] Hutton, Graham; Programming in Haskell, - Draft distributed as


www resource, Graham Hutton, Sept 10, 2004

[lva-00] lvanovic, Mi~ana; Kuneak, Viktor ; Modular Language


Specifications in Haskell; Institute of Mathematics, University of Novi Sad,
Yugoslavia, 2000
I

[lve-""]lversen, Jergen; Actions Semantics and Compiler Generators,


www resource

[Joh-89] Johanson, Anna-Lenn; Erikson-Granskog, Agneta; Edman,


Annell ;Prolog versus you, Springer Verlag, 1989

[Jon-93] Jones, Mark P; Duponcheel, Luc; Composing Monads; Research


Report YALEU I DCS I RR-1004, Dec.1993

[Jon-99] Jones, Mark P; Peterson , John C.; Hugs 98 - A functional


programming system based on Haskell 98- Revised Version : September
10,1999

[Kris-04] Krishnamurthi,Shriram; Programming Languages:


Applicationand Interpretation, Brown University,
2004, (www.resource)

[Koo-01] Koopman,Pieter; Plasmeijer, Rinus; Van Eekelen, Marco;


Smeisers, Sjaak - Functional Programming in Clean - Draft- September
10,2001 , (Part II, Cap 5,6,8)

0 introducere in Haskell 98 prin example - 306 - Dan Popa


[Lab-**] J.E. Labra Gayo, J.M. Cueva Lovelle, M.C. Luengo Diez,
B.M. Gonzalez Rodriguez - A language prototyping tool based on
Semantic Building Blocks- dept.of Comp. Sci., Univ. of Oviedo, -
www resource

[Lam-03] Lammel , Ralf; Peyton Jones, Simon; Scrap Your Boilerplate: A


Practical Design Pattern for Generic Programming , REPORT SEN-E0323
December 23, 2003, Centrum voor Wiskunde en Informatica

[Lei-01] Leljen, Daan; Parsec a fast combinator parser, Univ. of Utrecht,


Dept. Of Computer Science, Utrecht, The Nederlands, 4 oct 2001 , www
resource

(Lia-95] Liang,S; Hudak, P; Jones M.; Monad transformers and modular


interpreters , POPL'95. ACM Press 1995

[Lju-02] Ljunglof, Peter; Pure Functional Parsing, an advanced tutorial;


Dept. of Comp. Sci. Chalmers Univ. of tech. And Goteborg Univ., 2002

(Lut-02] LOth, Cristoph; Ghani, Neil; Composing Monads Using


Coproducts,ICFP 2002, Oct. 4-6, 2002, Pittsburg, Pennsylvania, ACM
2002

[Med-03] Medak, Damir; Navratil, Gerhard; Haskell-Tutorial, Institute for


Geoinformation, Technical University Vienna, Febr. 2003

(Mog-89a] Moggi, Eugenio ; Computational lambda calculus and monads,


Symposium on Logic in Computer Science,

0 introducere in Haskell 98 prin exemple - 307 - DanPopa


California, IEEE June 1989 ,(0 versiune mai lunga este disponibila ca

Raport al Universitatii din Edinburgh.)

[Mog89b] Moggi, Eugenio - An abstract view of programming


languages, Course notes, Univ. Edinburgh, 1989.

[Mog-90] Moggi, Eugenio; An abstract View of Programming Languages.


Course notes, University of Edinburgh,
Technical Report ECS-LFCS-90-113, Laboratory for Foundations of
Computer Science, University of Edinburgh, Scotland, 1990

[Mor-01] Moreno-Seco, Francisco; Forcada, MikelL; Learning compiler


design as a research activity, department de Lenguatges i Sistemas,
Informatics, Universitat d'Aiacant , E-03071 , Alacant, Spain

[Nie-**] Niemann, Thomas; A Compact Guide to Lex and Yacc, www


resource de Ia epaperpress.com

[Nie-99] Nielson, Hanne Riis; Nielson Flemming- Semantics with


applications- A formal introduction , 1999 - www resource

[Obr-98] Obradovic, Davor; Structuring functional programs by using


monads; University of Pensylvania, May 1998 - www resource

[Par-93] Parr, Terence John; Language Translation Using PCCTS and C+


+,A Reference Guide, Automata Publishing Company, San Jose, CA,
1993, ISBN-0-9627488-5-4

[**-**]Parsifal Software;Four Ways to Write an Interpreter

0 introducere in Haskell 98 prin example - 308 - DanPopa


r)

http: /lwww.parsifalsoft.com/examples/xideklxidek/doclfourways.htm

(Par-96] Parv Bazil, Vancea Alexandru; Fundamentele limbajelor de


programare, Editura AlbastrA . Cluj Napoca, 1996

[Pey-02] Peyton Jones, Simon (editor) ; Haskell 98 Languages and


Ubraries- The Revised Report, Cambridge, September 2002

[Pop-05] Popa, Dan; An assembler in a nutshell- Proceedings of the 30~~'~


annual Congress of The American-Romanian Academy of Art and
Sciences (ARA), Academia de Studii Economlce din Moldova, 2005, p.
196-198

[Pop-04] Popa, Dan; About the development of a pure interpreter system


by bootstrapping using level-level-compilers for a hierarchy of extensible
languages , Univ. BacAu, Studii ~i CercetAri $tiintifice- Seria MatematicA
Nr 14 (2004) p 117-128

[Pop-06] Popa, Dan; lntroducere in Haskell 98 prin example manuscris


nepublicat; 16 feb. 2006

[Pop-06] Papa, Dan ; Adaptive DFA based on array of sets- Univ. BacAu,
studii ~~ cercetAri ~tiintifice- Seria MatematlcA, Nr 15 (20050 pag 113-121

[Reu-**1 Reus Bernard , Lecture 12 - Monads, www resource

[Shu-03] Shutt, N. John ; Monads for Programming Languages, Computer


Science Technical Report Series, Worcester Polytechnic Institute, WPI-
CS-TR-03-21, June 2003

0 introducere in Haskell 98 prin example - 309 - DanPopa


[Spi-05) Spivey, Mike; Programming Languages, Laboratory Manual,
Oxford University Computing Laboratory, 2004-2005

[Spe-**1 Sperber, Michael, Thiemann, Peter, Realistic Compilation By


Partial Evaluation , Wilhelm-Schickard-lnstitut fOr lnformatik, Universitat
TO bingen - www resource

[Ste-94] Steele Jr., Guy L.; Building Interpreters by Composing Monads,


Thinking Machines Corporation, Cambridge, Massachusetts -
Proceedings of the Twenty-first Annual ACM SIGPLAN-SIGACT
Symposium on Principles of Programming Languages, Portland,Oregon,
January 1994

[Tan-05] Tang, Autrijus; PUGS, Bootstrapping Pert 6 with Haskell, ACM


Haskeii'05, sept 30, 2005, Talinn, Estonia.

[Ter-96] Terry, P.O.; Compilers an Compilers Generators, an introduction


with C++ , P.O. Terry, Rhodes University,
1996

[Zen-04] Zenger, Matthias; Programming Language Abstractions for


Extensible Software Components, Doctoral Thesis, EPFL, Switzerland,
2004

[Wad-92a] Wadler, Philip; Comprehending monads. Mathematical


Structures in Computer Science, 2, 1992

[Wad-92b] Wadler, Philip; The essence of functional programming, The


19'th Symposium on Principles of Programming Languages,
(Albuquerque), New Mexico; ACM, 1992

0 introducere in Haskell 98 prin example - 310 - DanPopa


[Wad-92c] Wadler,Philip; Monads for functional programming. Broy,
Manfred (ed), Proc. Mark1oberdorf Summer school on program design
calculi, Springer-Verlag, 1992

[Wad-95] Wadler Philip- How to declare an imperative, International


Logic Programming Symposium, MIT Press, 1995

[Wir-76] Wirth, Niklaus, Algorithms+ Data Structure= Programs,


Prentice-Hall, Englewood Clifs, N.J. 1976

[Zen-04) Zenger, Matthias; Programming Language- Abstractions for


Extensible Software Components, Doctoral Thesis, EPFL, Lausanne,
2004

[**-01] Webster's New Century Dictionary, Gramercy Books,


Random House, New York, 2001

["*-OS] Wikipedia, the free encyclopedia- Monads in functional


programming- 28 dec. 2005 http://en.wikipedia.org/

0 introducere in Haske/198 prin example- 311 - Dan Popa


Haskell este un limbaj functional, de uz general, pentru care exista o
serie de implementari libere. Poate fi folosit pe mai multe sisteme de
operare: Windows, Linux, Mac OS sau FreeBSD ori Solaris. Este
utilizabil din mediul de programare Visual Studio, din Eclipse sau din
Kdeve/op ori din mai noui/DE numit Leksah (de Ia www.leksah.org).

Cartea este simultan un manual introductiv de Haskell $i o carte


auxiliara pentru studenfii de Ia cursu/ de limbaje forma/e. Vefi avea
satisfactia cunoa$terii unui limbaj modem extrem de productiv
(pentru anumite feluri de aplicatii de peste 10 ori mai productiv ca
alte limbaje) fn care algoritmul de sortare Quicksort se scrie pe 6
randuri 8$8 cum se poate de altfel vedea $i fn imaginea de pe
coperta I.

Aceasta carte f$i propune sa va faca tovara$i de drum ai autorului fn


fncercarea de a parcurge calea de Ia condifia de programator in
C sau Pascal Ia cea de "type safe programmer" care lucreaza in
Haskell $1 obfine o productivitate de circa 10 ori mai mare a muncii.

Cartea cuprinde o serie de capitola folosite Ia Universitates Bacau rn


calitate de auxiliare de /aborator Ia obiectul Umbaje Forma/e. Lipsa
unor manuale romfme$fi de Haskell din biblioteci ne-a fflcut sa
publicam aceasta carte cu regretul ca mai sunt rnca multe de tacut.
Dan Popa este fondatorul comunit{Jfii Ro/Haske/1.

0 introducere Tn Haske/198 prin example - 312 - Dan Popa

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