This shows you the differences between two versions of the page.
pp:23:laboratoare:haskell:intro [2023/04/02 15:38] bot.pp |
pp:23:laboratoare:haskell:intro [2023/04/04 20:46] (current) bot.pp |
||
---|---|---|---|
Line 20: | Line 20: | ||
===== Introducere ===== | ===== Introducere ===== | ||
- | În jurul anilor 1990 un comitet de cercetători în limbaje de programare (Simon Marlow, Simon Peyton Jones, Philip Wadler etc) au creat un limbaj nou care a ajuns să fie standardul de-facto în cercetarea din domeniul programării funcționale. Inspirat dintr-o varietate de limbaje -- Miranda, ML, Scheme, APL, FP -- limbajul a influențat la rândul lui majoritatea limbajelor de programare cunoscute. | + | În jurul anilor 1990 un comitet de cercetători în limbaje de programare (Simon Marlow, Simon Peyton Jones, Philip Wadler etc) a creat un limbaj nou care a ajuns să fie standardul de-facto în cercetarea din domeniul programării funcționale. Inspirat dintr-o varietate de limbaje -- Miranda, ML, Scheme, APL, FP -- limbajul a influențat la rândul lui majoritatea limbajelor de programare cunoscute. |
**Haskell** este un limbaj funcțional **pur**. Spre deosebire de limbajele imperative (sau Racket unde există //set!//), în Haskell aproape toate funcțiile sunt pure. Funcțiile impure sunt marcate diferit prin intermediul sistemului de tipuri. | **Haskell** este un limbaj funcțional **pur**. Spre deosebire de limbajele imperative (sau Racket unde există //set!//), în Haskell aproape toate funcțiile sunt pure. Funcțiile impure sunt marcate diferit prin intermediul sistemului de tipuri. | ||
Line 28: | Line 28: | ||
Deși tipurile există, programatorul nu este obligat să depună efort în a le scrie în program. Haskell deține inferență de tipuri puternică. Exceptând cazurile în care se folosesc concepte avansate, programatorul poate lucra fără a scrie o singură semnătură de funcție (deși nu e recomandat pentru că se pierde o parte din documentația funcției). | Deși tipurile există, programatorul nu este obligat să depună efort în a le scrie în program. Haskell deține inferență de tipuri puternică. Exceptând cazurile în care se folosesc concepte avansate, programatorul poate lucra fără a scrie o singură semnătură de funcție (deși nu e recomandat pentru că se pierde o parte din documentația funcției). | ||
- | În plus, tipurile ajută programatorul în procesul de scriere a codului transformându-l într-un exercițiu de rezolvare a unui puzzle: pur și simplu trebuiesc potrivite tipurile folosind funcții existente sau scriind alte funcții. Pentru a căuta funcțiile ce respectă o semnătură se poate folosi [[http://www.haskell.org/hoogle/|Hoogle]], un motor de căutare similar Google dar doar pentru funcții Haskell. | + | În plus, tipurile ajută programatorul în procesul de scriere a codului transformându-l într-un exercițiu de rezolvare a unui puzzle: pur și simplu trebuiesc potrivite tipurile folosind funcții existente sau scriind alte funcții. Pentru a căuta funcțiile ce respectă o semnătură se poate folosi [[https://hoogle.haskell.org|Hoogle]], un motor de căutare similar Google dar doar pentru funcții Haskell. |
Deosebirea fundamentală față de alte limbaje este **evaluarea leneșă**. Funcțiile nu vor fi evaluate și expresiile nu vor fi reduse până în momentul în care valoarea lor este necesară. Programatorul poate astfel lucra cu date infinite, extrăgând din ele strictul necesar pentru a obține soluția. Ca dezavantaj, analiza performanței unui cod Haskell este puțin mai dificilă, dar există instrumente auxiliare (dezvoltate în Haskell). | Deosebirea fundamentală față de alte limbaje este **evaluarea leneșă**. Funcțiile nu vor fi evaluate și expresiile nu vor fi reduse până în momentul în care valoarea lor este necesară. Programatorul poate astfel lucra cu date infinite, extrăgând din ele strictul necesar pentru a obține soluția. Ca dezavantaj, analiza performanței unui cod Haskell este puțin mai dificilă, dar există instrumente auxiliare (dezvoltate în Haskell). | ||
Line 83: | Line 83: | ||
* se rulează în interpretor apelurile de funcții de test necesare, verificarea de tipuri, etc | * se rulează în interpretor apelurile de funcții de test necesare, verificarea de tipuri, etc | ||
* dacă se dorește **editarea fișierului** se poate face într-un terminal separat (recomandat) sau folosind ''%%:e%%'' (''%%:edit%%'') din ''%%ghci%%'' | * dacă se dorește **editarea fișierului** se poate face într-un terminal separat (recomandat) sau folosind ''%%:e%%'' (''%%:edit%%'') din ''%%ghci%%'' | ||
- | * ATENTIE! după editarea fișierului, pentru a reîncărca definițiile se folosește ''%%:re%%'' (''%%:reload%%'') | + | * ATENTIE! după editarea fișierului, pentru a reîncărca definițiile se folosește ''%%:r%%'' (''%%:reload%%'') |
* dacă se dorește încărcarea altor module în interpretor (pentru testare), se poate folosi ''%%:m +Nume.Modul%%'' | * dacă se dorește încărcarea altor module în interpretor (pentru testare), se poate folosi ''%%:m +Nume.Modul%%'' | ||
* pentru a verifica tipul unor expresii se folosește ''%%:t expresie%%'' | * pentru a verifica tipul unor expresii se folosește ''%%:t expresie%%'' | ||
- | * pentru a ieși din interpretor se folosește ''%%:q%%'' (sau EOF - ''%%^Z%%'' pe Linux/OSX, ''%%^D%%'' pe Windows) | + | * pentru a ieși din interpretor se folosește ''%%:q%%'' (sau caracterul ''%%EOF%%'', prin combinația de taste ''%%^D%%'') |
Modulul ''%%Prelude%%'' conține funcții des folosite și este inclus implicit în orice fișier (deși poate fi exclus la nevoie, consultați [[#referințe|bibliografia]]). | Modulul ''%%Prelude%%'' conține funcții des folosite și este inclus implicit în orice fișier (deși poate fi exclus la nevoie, consultați [[#referințe|bibliografia]]). | ||
Line 142: | Line 142: | ||
</code> | </code> | ||
- | Observați că se renunță la paranteze. Unul dintre principiile dezvoltării limbajului a fost oferirea de construcții cât mai simple pentru lucrurile folosite cel mai des. Fiind vorba de programarea funcțională, aplicarea de funcții trebuia făcută cât mai simplu posibil. | + | Observați că se renunță la paranteze. Unul dintre principiile dezvoltării limbajului a fost oferirea de construcții cât mai simple pentru lucrurile folosite cel mai des. Fiind vorba de programarea funcțională, aplicarea de funcții trebuia făcută cât mai simplă cu putință. |
- | Tot din același considerent, operatorii în Haskell sunt scriși în forma **infixată**. Restul apelurilor de funcții sunt în forma prefixată, exact ca în Racket. Totuși, se pot folosi ambele forme: pentru a folosi un operator prefixat se folosesc paranteze, în timp ce pentru a infixa o funcție se folosesc //back-quotes// (`). | + | Tot din același considerent, operatorii în Haskell sunt scriși în forma **infixată**. Restul apelurilor de funcții sunt în forma prefixată, exact ca în Racket. Totuși, se pot folosi ambele forme: pentru a folosi un operator în forma lui prefixată se folosesc paranteze, în timp ce pentru a infixa o funcție se folosesc //back-quotes// (`). |
<code> | <code> | ||
Line 160: | Line 160: | ||
</code> | </code> | ||
- | **Atenție**: În construcția ''%%(- x)%%'' operatorul ''%%-%%'' este unar, nu binar (este echivalentul funcției ''%%negate%%''). Dacă doriți să aplicați pațial la dreapta operatorul de scădere, utilizați funcția ''%%subtract%%'', ca în expresia ''%%(\%%''subtract` 2)`. | + | **Atenție**: În construcția ''%%(- x)%%'' operatorul ''%%-%%'' este unar, nu binar (este echivalentul funcției ''%%negate%%''). Dacă doriți să aplicați pațial la dreapta operatorul de scădere, utilizați funcția ''%%subtract%%'', ca în expresia ''%%(`subtract` 2)%%''. |
===== Tipuri de bază ===== | ===== Tipuri de bază ===== | ||
Line 166: | Line 166: | ||
În această secțiune vom prezenta tipurile existente în limbajul Haskell. Veți observa că limbajul este mult mai bogat în tipuri decât Racket. Programatorul își va putea defini alte tipuri proprii dacă dorește. | În această secțiune vom prezenta tipurile existente în limbajul Haskell. Veți observa că limbajul este mult mai bogat în tipuri decât Racket. Programatorul își va putea defini alte tipuri proprii dacă dorește. | ||
- | Pentru a putea vedea tipul unei expresii în ''%%ghci%%'' folosiți ''%%:t expresie%%''. | + | Pentru a vedea tipul unei expresii în ''%%ghci%%'' folosiți ''%%:t expresie%%''. |
<code> | <code> | ||
Line 182: | Line 182: | ||
==== Numere, caractere, siruri, booleeni ==== | ==== Numere, caractere, siruri, booleeni ==== | ||
- | Următorii literali sunt valizi in **Haskell** (dupa operatorul ''%%::%%'' sunt precizate tipurile acestora): | + | Următorii literali sunt valizi in **Haskell** (după operatorul ''%%::%%'' sunt precizate tipurile acestora): |
<code> | <code> | ||
Line 192: | Line 192: | ||
</code> | </code> | ||
- | Observați că există tipul caracter și tipul șir de caractere. Tipul ''%%String%%'' este de fapt un sinonim pentru tipul ''%%[Char]%%'' - tipul listă de caractere. Astfel, operațiile pe [[#liste|liste]] vor funcționa și pe șiruri. | + | Observați că există tipul //caracter// și tipul //șir de caractere//. Tipul ''%%String%%'' este de fapt un sinonim pentru tipul ''%%[Char]%%'' - tipul //listă de caractere//. Astfel, operațiile pe [[#liste|liste]] vor funcționa și pe șiruri de caractere. |
Tipurile numerice sunt puțin diferite față de alte limbaje de programare cunoscute: | Tipurile numerice sunt puțin diferite față de alte limbaje de programare cunoscute: | ||
Line 201: | Line 201: | ||
</code> | </code> | ||
- | În exemplul de mai sus, ''%%a%%'' este o variabilă de tip (stă pentru orice fel de tip) restricționată (prin folosirea ''%%=>%%'') la toate tipurile numerice (''%%Num a%%''). | + | În exemplul de mai sus, ''%%a%%'' este o variabilă de tip (ține locul oricărui tip) restricționată (prin folosirea ''%%=>%%'') doar la tipurile numerice (''%%Num a%%''). |
- | Important de reținut este faptul că există **2** tipuri întregi: ''%%Int%%'' și ''%%Integer%%''. Primul este finit, determinat de arhitectură, în timp ce al doilea este infinit, putând ajunge oricât de mare. | + | Important de reținut este faptul că există **2** tipuri pentru numere întregi: ''%%Int%%'' și ''%%Integer%%''. Primul este finit, determinat de arhitectură, în timp ce al doilea este infinit, putând ajunge oricât de mare (vă puteți imagina că este implementat ca o listă de cifre). |
==== Liste ==== | ==== Liste ==== | ||
Line 217: | Line 217: | ||
<code> | <code> | ||
- | 1:3:5:[] | + | 1:3:5:7:9:[] |
</code> | </code> | ||
Line 223: | Line 223: | ||
<code> | <code> | ||
- | [1, 3, 5] | + | [1, 3, 5, 7, 9] |
</code> | </code> | ||
Line 229: | Line 229: | ||
<code> | <code> | ||
- | [1, 3 .. 6] | + | [1, 3 .. 9] -- prelungește lista [1, 3] păstrând același "pas" (2) până la o valoare maximă (9) |
</code> | </code> | ||
Line 235: | Line 235: | ||
<code> | <code> | ||
- | [1, 3 .. 5] | + | [1, 3 .. 10] -- limita dreaptă a listei nu trebuie să fie specificată precis |
</code> | </code> | ||
Line 319: | Line 319: | ||
</code> | </code> | ||
- | Expresiile din interiorul fiecărei ramuri din ''%%case%%'' sau din interiorul pattern-match nu pot fi decât constructorii unui tip (similar cu ce ați făcut la AA). În continuare vom folosi cele 4 stiluri pentru a ilustra funcția care calculează lungimea unei liste | + | Expresiile din interiorul fiecărei ramuri din ''%%case%%'' sau din interiorul pattern-match nu pot fi decât constructorii unui tip (asemenea analizei pe cazuri a TDA-urilor în inducția structurală învățată la AA). În continuare vom folosi cele 4 stiluri pentru a ilustra funcția care calculează lungimea unei liste |
<code> | <code> | ||
- | length_if l = if l /= [] then 1 + length_if (tail l) else 0 | + | length_if l = if l == [] then 0 else 1 + length_if (tail l) |
</code> | </code> | ||
<code> | <code> | ||
length_guard l | length_guard l | ||
- | | l /= [] = 1 + length_guard (tail l) | + | | l == [] = 0 |
- | | otherwise = 0 | + | | otherwise = 1 + length_guard (tail l) |
</code> | </code> | ||
<code> | <code> | ||
length_case l = case l of | length_case l = case l of | ||
+ | [] -> 0 | ||
(_ : xs) -> 1 + length_case xs | (_ : xs) -> 1 + length_case xs | ||
- | _ -> 0 | ||
</code> | </code> | ||
Line 357: | Line 357: | ||
</code> | </code> | ||
- | Fiecare argument este separat prin ''%%->%%'' de următorul sau de rezultat. | + | Fiecare argument este separat prin ''%%->%%'' de următorul argument (respectiv de rezultat în cazul ultimului argument). Operatorul ''%%->%%'' este asociativ la dreapta. De aceea, semnătura ''%%a -> b -> c%%'' este echivalentă cu ''%%a -> (b -> c)%%''. Ambele descriu o funcție care primește, pe rând, un argument de tip ''%%a%%'' și unul de tip ''%%b%%'', întorcând un rezultat de tip ''%%c%%''. |
Amintindu-ne de discuția despre [[https://ocw.cs.pub.ro/courses/pp/22/laboratoare/racket/functionale#functii_curryuncurry|funcții curry și uncurry]], rezultatul următor nu trebuie să ne surprindă | Amintindu-ne de discuția despre [[https://ocw.cs.pub.ro/courses/pp/22/laboratoare/racket/functionale#functii_curryuncurry|funcții curry și uncurry]], rezultatul următor nu trebuie să ne surprindă | ||
Line 368: | Line 368: | ||
Toate funcțiile din Haskell sunt în formă **curry**. | Toate funcțiile din Haskell sunt în formă **curry**. | ||
- | Pentru a face o funcție uncurry putem folosi funcția ''%%uncurry :: (a -> b -> c) -> (a, b) -> c%%'' dacă vrem să transformăm o funcție echivalentă sau putem s-o definim în mod direct folosind perechi. | + | Putem transforma o funcție curry (adică o funcție obișnuită) într-una "uncurry" folosind ''%%uncurry :: (a -> b -> c) -> (a, b) -> c%%''. Alternativ, putem defini în mod direct o funcție uncurry folosind perechi. |
+ | <code> | ||
+ | add :: (Int, Int) -> Int | ||
+ | add (x, y) = x + y | ||
+ | |||
+ | add (2, 3) -- 5 | ||
+ | |||
+ | </code> | ||
===== Funcționale uzuale ===== | ===== Funcționale uzuale ===== | ||
- | Haskell oferă un set de funcționale (similar celui din Racket) pentru cazuri de folosire des întâlnite (dacă nu mai știți ce fac, încercați să ghiciți citind semnătura și numele ca o documentație): | + | Haskell oferă un set de funcționale (similar celui din Racket) pentru situații uzuale (dacă nu mai știți ce fac, încercați să ghiciți citind semnătura și numele ca pe o formă de documentație): |
<code> | <code> | ||
> :t map | > :t map | ||
- | map :: (a -> b) -> [a] -> [b] | + | map :: (a -> b) -> [a] -> [b] -- "`map` primește o funcție care "transformă a-uri în b-uri" și o listă de a-uri și întoarce .. o listă de b-uri! |
> :t filter | > :t filter | ||
filter :: (a -> Bool) -> [a] -> [a] | filter :: (a -> Bool) -> [a] -> [a] | ||
Line 389: | Line 396: | ||
</code> | </code> | ||
- | Folosirea lor duce la un cod mai ușor de citit și de întreținut. | + | Folosirea lor conduce la un cod mai ușor de citit și de întreținut. De remarcat că ''%%zip%%'' nu este o funcțională, deoarece nu primește ca parametri funcții și nici nu întoarce funcții, dar este prezentată comparativ cu funcționala ''%%zipWith%%'' prin prisma tipurilor. Comportamental, ''%%zip = zipWith (,)%%'', unde ''%%(,)%%'' este constructorul perechilor, înțeles ca o funcție binară, care primește ca parametri cele două componente ale unei perechi. |
===== Domenii de vizibilitate ===== | ===== Domenii de vizibilitate ===== | ||
Line 441: | Line 448: | ||
cu aceleași observații ca în cazul ''%%let%%''. | cu aceleași observații ca în cazul ''%%let%%''. | ||
- | Un exemplu de folosire este implementarea metodei de sortare QuickSort: | + | Un exemplu de folosire vedem în implementarea metodei de sortare QuickSort: |
<code> | <code> | ||
Line 475: | Line 482: | ||
''%%iter%%'' având în exemplul de mai sus rolul de generator auxiliar al listei numerelor naturale. | ''%%iter%%'' având în exemplul de mai sus rolul de generator auxiliar al listei numerelor naturale. | ||
- | ===== Traduceri cod din Racket în Haskell ===== | + | ===== Racket vs Haskell ===== |
Aici, vom face comparații între Racket și Haskell, în ceea ce privește sintaxa, pe categorii. | Aici, vom face comparații între Racket și Haskell, în ceea ce privește sintaxa, pe categorii. | ||
Line 513: | Line 520: | ||
2 * 11 | 2 * 11 | ||
5 / 2 | 5 / 2 | ||
- | mod 7 2 | + | mod 7 2 -- sau 7 `mod` 2 |
- | quot 7 2 | + | quot 7 2 -- sau 7 `quot` 2 |
</code> | </code> | ||
Line 561: | Line 568: | ||
</code> | </code> | ||
- | * Haskell - sunt tupluri, mai precis colecții care pot avea elemente de tipuri diferite | + | * Haskell - folosim tupluri, mai precis colecții care pot avea elemente de tipuri diferite |
<code> | <code> | ||
Line 618: | Line 625: | ||
</code> | </code> | ||
- | Sintaxa if: | + | Sintaxa ''%%if%%'': |
* Racket | * Racket | ||
Line 654: | Line 661: | ||
<code> | <code> | ||
- | -- cu if-else-then | + | -- cu `if .. else .. then` |
sumList :: [Int] -> Int | sumList :: [Int] -> Int | ||
sumList l = if null l then 0 else head l + sumList (tail l) | sumList l = if null l then 0 else head l + sumList (tail l) | ||
- | -- pattern matching | + | -- cu gărzi |
- | sumList2 :: [Int] -> Int | + | |
- | sumList2 [] = 0 | + | |
- | sumList2 (x:xl) = x + sumList2 xl | + | |
- | + | ||
- | -- cu garzi | + | |
sumList3 :: [Int] -> Int | sumList3 :: [Int] -> Int | ||
sumList3 l | sumList3 l | ||
Line 669: | Line 671: | ||
| otherwise = head l + sumList3 (tail l) | | otherwise = head l + sumList3 (tail l) | ||
- | -- cu case of | + | -- cu `case .. of` |
sumList4 :: [Int] -> Int | sumList4 :: [Int] -> Int | ||
sumList4 l = case l of | sumList4 l = case l of | ||
[] -> 0 | [] -> 0 | ||
(x:xl) -> x + sumList4 xl | (x:xl) -> x + sumList4 xl | ||
+ | |||
+ | -- cu pattern matching (sintactic sugar pentru varianta cu `case` de mai sus) | ||
+ | sumList2 :: [Int] -> Int | ||
+ | sumList2 [] = 0 | ||
+ | sumList2 (x:xl) = x + sumList2 xl | ||
</code> | </code> | ||
Line 682: | Line 689: | ||
<code lisp> | <code lisp> | ||
; map | ; map | ||
- | (map (λ (x) (+ x 1)) (list 1 2 3 4)) ; '(2 3 4 5) | + | (map (λ (x) (+ x 1)) (list 1 2 3 4)) ; '(2 3 4 5) |
- | (map add1 (list 1 2 3 4)) ; '(2 3 4 5) | + | (map add1 (list 1 2 3 4)) ; '(2 3 4 5) |
; filter | ; filter | ||
- | (filter (λ (x) (equal? (modulo x 2) 0)) (list 1 2 3 4 5 6)) ; '(2 4 6) | + | (filter (λ (x) (equal? (modulo x 2) 0)) (list 1 2 3 4 5 6)) ; '(2 4 6) |
; foldl | ; foldl | ||
- | (reverse (foldl (lambda (x acc) (cons x acc)) '() (list 1 2 3 4 5))) ; '(1 2 3 4 5) | + | (reverse (foldl (lambda (x acc) (cons x acc)) '() (list 1 2 3 4 5))) ; '(1 2 3 4 5) |
; foldr | ; foldr | ||
Line 699: | Line 706: | ||
<code> | <code> | ||
-- map | -- map | ||
- | map (\x -> x + 1) [1, 2, 3, 4] -- [2, 3, 4, 5] | + | map (\x -> x + 1) [1, 2, 3, 4] -- [2, 3, 4, 5] |
- | map (+ 1) [1, 2, 3, 4] -- [2, 3, 4, 5] | + | map (+ 1) [1, 2, 3, 4] -- [2, 3, 4, 5] |
-- filter | -- filter | ||
- | filter (\x -> mod x 2 == 0) [1, 2, 3, 3, 4, 5, 6] -- [2, 4, 6] | + | filter (\x -> mod x 2 == 0) [1, 2, 3, 3, 4, 5, 6] -- [2, 4, 6] |
-- foldl | -- foldl | ||
- | reverse $ foldl (\acc x -> x : acc) [] [1, 2, 3, 4, 5] -- [1, 2, 3, 4, 5] | + | reverse $ foldl (\acc x -> x : acc) [] [1, 2, 3, 4, 5] -- [1, 2, 3, 4, 5] |
-- foldr | -- foldr | ||
- | foldr (\x acc -> x : acc) [] [1, 2, 3, 4, 5] -- [1, 2, 3, 4, 5] | + | foldr (\x acc -> x : acc) [] [1, 2, 3, 4, 5] -- [1, 2, 3, 4, 5] |
</code> | </code> | ||
- | Un fapt notabil în ceea ce diferența dintre Haskell și Racket este în faptul că diferă ordinea parametrilor la funcția anonimă dată ca parametru între tipurile de fold (în Racket nu diferă ordinea, care este element -> acumulator, acest fapt se poate observa în exemplele de mai sus), mai precis: | + | Observăm că, spre deosebire de Racket, în Haskell funcționalele ''%%foldl%%'' și ''%%foldr%%'' primesc funcții cu semnături diferite: |
- | * ''%%foldl%%'': acumulator -> element | + | * ''%%foldl :: (b -> a -> b) -> b -> [a] -> b%%'' (funcția ajutătoare primește acumulatorul și apoi elementul curent) |
- | * ''%%foldr%%'': element -> acumulator | + | * ''%%foldr :: (a -> b -> b) -> b -> [a] -> b%%'' (funcția ajutătoare primește elementul curent și //apoi// acumulatorul) |
Legări: | Legări: | ||
Line 752: | Line 759: | ||
let c = b | let c = b | ||
b = a + 1 | b = a + 1 | ||
- | in (c + b) -- letrec din Racket, aici nu avem eroare datorita evaluarii lenese | + | in (c + b) -- letrec din Racket, aici nu avem eroare datorită evaluării leneșe |
-- cu where | -- cu where | ||
Line 768: | Line 775: | ||
where | where | ||
c = b | c = b | ||
- | b = a + 1 -- letrec din Racket, aici nu avem eroare datorita evaluarii lenese | + | b = a + 1 -- letrec din Racket, aici nu avem eroare datorită evaluării leneșe |
</code> | </code> |